Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=8 sts=4 et sw=4 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/. */
7 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/DebugOnly.h"
12 #include "necko-config.h"
14 #include "nsCache.h"
15 #include "nsCacheService.h"
16 #include "nsCacheRequest.h"
17 #include "nsCacheEntry.h"
18 #include "nsCacheEntryDescriptor.h"
19 #include "nsCacheDevice.h"
20 #include "nsMemoryCacheDevice.h"
21 #include "nsICacheVisitor.h"
22 #include "nsDiskCacheDevice.h"
23 #include "nsDiskCacheDeviceSQL.h"
24 #include "nsCacheUtils.h"
25 #include "../cache2/CacheObserver.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 "nsVoidArray.h"
37 #include "nsDeleteDir.h"
38 #include "nsNetCID.h"
39 #include <math.h> // for log()
40 #include "mozilla/Services.h"
41 #include "nsITimer.h"
42 #include "mozIStorageService.h"
44 #include "mozilla/net/NeckoCommon.h"
45 #include "mozilla/VisualEventTracer.h"
46 #include <algorithm>
48 using namespace mozilla;
50 /******************************************************************************
51 * nsCacheProfilePrefObserver
52 *****************************************************************************/
53 #define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
54 #define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
55 #define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
56 "browser.cache.disk.smart_size.first_run"
57 #define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
58 "browser.cache.disk.smart_size.enabled"
59 #define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
60 #define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
61 #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
62 #define DISK_CACHE_CAPACITY 256000
64 #define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
65 "browser.cache.disk.smart_size.use_old_max"
67 #define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
68 #define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
69 #define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
70 #define OFFLINE_CACHE_CAPACITY 512000
72 #define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
73 #define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
74 #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
76 #define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
77 #define CACHE_COMPRESSION_LEVEL 1
79 #define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
80 #define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
82 static const char * observerList[] = {
83 "profile-before-change",
84 "profile-do-change",
85 NS_XPCOM_SHUTDOWN_OBSERVER_ID,
86 "last-pb-context-exited",
87 "suspend_process_notification",
88 "resume_process_notification"
89 };
91 static const char * prefList[] = {
92 DISK_CACHE_ENABLE_PREF,
93 DISK_CACHE_SMART_SIZE_ENABLED_PREF,
94 DISK_CACHE_CAPACITY_PREF,
95 DISK_CACHE_DIR_PREF,
96 DISK_CACHE_MAX_ENTRY_SIZE_PREF,
97 DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
98 OFFLINE_CACHE_ENABLE_PREF,
99 OFFLINE_CACHE_CAPACITY_PREF,
100 OFFLINE_CACHE_DIR_PREF,
101 MEMORY_CACHE_ENABLE_PREF,
102 MEMORY_CACHE_CAPACITY_PREF,
103 MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
104 CACHE_COMPRESSION_LEVEL_PREF,
105 SANITIZE_ON_SHUTDOWN_PREF,
106 CLEAR_ON_SHUTDOWN_PREF
107 };
109 // Cache sizes, in KB
110 const int32_t DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
111 #ifdef ANDROID
112 const int32_t MAX_CACHE_SIZE = 200 * 1024; // 200 MB
113 const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024; // 200 MB
114 #else
115 const int32_t MAX_CACHE_SIZE = 350 * 1024; // 350 MB
116 const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
117 #endif
118 // Default cache size was 50 MB for many years until FF 4:
119 const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
121 class nsCacheProfilePrefObserver : public nsIObserver
122 {
123 public:
124 NS_DECL_THREADSAFE_ISUPPORTS
125 NS_DECL_NSIOBSERVER
127 nsCacheProfilePrefObserver()
128 : mHaveProfile(false)
129 , mDiskCacheEnabled(false)
130 , mDiskCacheCapacity(0)
131 , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
132 , mSmartSizeEnabled(false)
133 , mShouldUseOldMaxSmartSize(false)
134 , mOfflineCacheEnabled(false)
135 , mOfflineCacheCapacity(0)
136 , mMemoryCacheEnabled(true)
137 , mMemoryCacheCapacity(-1)
138 , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
139 , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
140 , mSanitizeOnShutdown(false)
141 , mClearCacheOnShutdown(false)
142 {
143 }
145 virtual ~nsCacheProfilePrefObserver() {}
147 nsresult Install();
148 void Remove();
149 nsresult ReadPrefs(nsIPrefBranch* branch);
151 bool DiskCacheEnabled();
152 int32_t DiskCacheCapacity() { return mDiskCacheCapacity; }
153 void SetDiskCacheCapacity(int32_t);
154 int32_t DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
155 nsIFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
156 bool SmartSizeEnabled() { return mSmartSizeEnabled; }
158 bool ShouldUseOldMaxSmartSize() { return mShouldUseOldMaxSmartSize; }
159 void SetUseNewMaxSmartSize(bool useNew) { mShouldUseOldMaxSmartSize = !useNew; }
161 bool OfflineCacheEnabled();
162 int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
163 nsIFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
165 bool MemoryCacheEnabled();
166 int32_t MemoryCacheCapacity();
167 int32_t MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
169 int32_t CacheCompressionLevel();
171 bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
173 static uint32_t GetSmartCacheSize(const nsAString& cachePath,
174 uint32_t currentSize,
175 bool shouldUseOldMaxSmartSize);
177 bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
179 private:
180 bool mHaveProfile;
182 bool mDiskCacheEnabled;
183 int32_t mDiskCacheCapacity; // in kilobytes
184 int32_t mDiskCacheMaxEntrySize; // in kilobytes
185 nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
186 bool mSmartSizeEnabled;
188 bool mShouldUseOldMaxSmartSize;
190 bool mOfflineCacheEnabled;
191 int32_t mOfflineCacheCapacity; // in kilobytes
192 nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
194 bool mMemoryCacheEnabled;
195 int32_t mMemoryCacheCapacity; // in kilobytes
196 int32_t mMemoryCacheMaxEntrySize; // in kilobytes
198 int32_t mCacheCompressionLevel;
200 bool mSanitizeOnShutdown;
201 bool mClearCacheOnShutdown;
202 };
204 NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
206 class nsSetDiskSmartSizeCallback MOZ_FINAL : public nsITimerCallback
207 {
208 public:
209 NS_DECL_THREADSAFE_ISUPPORTS
211 NS_IMETHOD Notify(nsITimer* aTimer) {
212 if (nsCacheService::gService) {
213 nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
214 nsCacheService::gService->SetDiskSmartSize_Locked();
215 nsCacheService::gService->mSmartSizeTimer = nullptr;
216 }
217 return NS_OK;
218 }
219 };
221 NS_IMPL_ISUPPORTS(nsSetDiskSmartSizeCallback, nsITimerCallback)
223 // Runnable sent to main thread after the cache IO thread calculates available
224 // disk space, so that there is no race in setting mDiskCacheCapacity.
225 class nsSetSmartSizeEvent: public nsRunnable
226 {
227 public:
228 nsSetSmartSizeEvent(int32_t smartSize)
229 : mSmartSize(smartSize) {}
231 NS_IMETHOD Run()
232 {
233 NS_ASSERTION(NS_IsMainThread(),
234 "Setting smart size data off the main thread");
236 // Main thread may have already called nsCacheService::Shutdown
237 if (!nsCacheService::IsInitialized())
238 return NS_ERROR_NOT_AVAILABLE;
240 // Ensure smart sizing wasn't switched off while event was pending.
241 // It is safe to access the observer without the lock since we are
242 // on the main thread and the value changes only on the main thread.
243 if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
244 return NS_OK;
246 nsCacheService::SetDiskCacheCapacity(mSmartSize);
248 nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
249 if (!ps ||
250 NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
251 NS_WARNING("Failed to set smart size pref");
253 return NS_OK;
254 }
256 private:
257 int32_t mSmartSize;
258 };
261 // Runnable sent from main thread to cacheIO thread
262 class nsGetSmartSizeEvent: public nsRunnable
263 {
264 public:
265 nsGetSmartSizeEvent(const nsAString& cachePath, uint32_t currentSize,
266 bool shouldUseOldMaxSmartSize)
267 : mCachePath(cachePath)
268 , mCurrentSize(currentSize)
269 , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
270 {}
272 // Calculates user's disk space available on a background thread and
273 // dispatches this value back to the main thread.
274 NS_IMETHOD Run()
275 {
276 uint32_t size;
277 size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
278 mCurrentSize,
279 mShouldUseOldMaxSmartSize);
280 NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
281 return NS_OK;
282 }
284 private:
285 nsString mCachePath;
286 uint32_t mCurrentSize;
287 bool mShouldUseOldMaxSmartSize;
288 };
290 class nsBlockOnCacheThreadEvent : public nsRunnable {
291 public:
292 nsBlockOnCacheThreadEvent()
293 {
294 }
295 NS_IMETHOD Run()
296 {
297 nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
298 #ifdef PR_LOGGING
299 CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
300 #endif
301 nsCacheService::gService->mCondVar.Notify();
302 return NS_OK;
303 }
304 };
307 nsresult
308 nsCacheProfilePrefObserver::Install()
309 {
310 // install profile-change observer
311 nsCOMPtr<nsIObserverService> observerService =
312 mozilla::services::GetObserverService();
313 if (!observerService)
314 return NS_ERROR_FAILURE;
316 nsresult rv, rv2 = NS_OK;
317 for (unsigned int i=0; i<ArrayLength(observerList); i++) {
318 rv = observerService->AddObserver(this, observerList[i], false);
319 if (NS_FAILED(rv))
320 rv2 = rv;
321 }
323 // install preferences observer
324 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
325 if (!branch) return NS_ERROR_FAILURE;
327 for (unsigned int i=0; i<ArrayLength(prefList); i++) {
328 rv = branch->AddObserver(prefList[i], this, false);
329 if (NS_FAILED(rv))
330 rv2 = rv;
331 }
333 // Determine if we have a profile already
334 // Install() is called *after* the profile-after-change notification
335 // when there is only a single profile, or it is specified on the
336 // commandline at startup.
337 // In that case, we detect the presence of a profile by the existence
338 // of the NS_APP_USER_PROFILE_50_DIR directory.
340 nsCOMPtr<nsIFile> directory;
341 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
342 getter_AddRefs(directory));
343 if (NS_SUCCEEDED(rv))
344 mHaveProfile = true;
346 rv = ReadPrefs(branch);
347 NS_ENSURE_SUCCESS(rv, rv);
349 return rv2;
350 }
353 void
354 nsCacheProfilePrefObserver::Remove()
355 {
356 // remove Observer Service observers
357 nsCOMPtr<nsIObserverService> obs =
358 mozilla::services::GetObserverService();
359 if (obs) {
360 for (unsigned int i=0; i<ArrayLength(observerList); i++) {
361 obs->RemoveObserver(this, observerList[i]);
362 }
363 }
365 // remove Pref Service observers
366 nsCOMPtr<nsIPrefBranch> prefs =
367 do_GetService(NS_PREFSERVICE_CONTRACTID);
368 if (!prefs)
369 return;
370 for (unsigned int i=0; i<ArrayLength(prefList); i++)
371 prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
372 }
374 void
375 nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
376 {
377 mDiskCacheCapacity = std::max(0, capacity);
378 }
381 NS_IMETHODIMP
382 nsCacheProfilePrefObserver::Observe(nsISupports * subject,
383 const char * topic,
384 const char16_t * data_unicode)
385 {
386 nsresult rv;
387 NS_ConvertUTF16toUTF8 data(data_unicode);
388 CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get()));
390 if (!nsCacheService::IsInitialized()) {
391 if (!strcmp("resume_process_notification", topic)) {
392 // A suspended process has a closed cache, so re-open it here.
393 nsCacheService::GlobalInstance()->Init();
394 }
395 return NS_OK;
396 }
398 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
399 // xpcom going away, shutdown cache service
400 nsCacheService::GlobalInstance()->Shutdown();
401 } else if (!strcmp("profile-before-change", topic)) {
402 // profile before change
403 mHaveProfile = false;
405 // XXX shutdown devices
406 nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse",
407 data.get()));
409 } else if (!strcmp("suspend_process_notification", topic)) {
410 // A suspended process may never return, so shutdown the cache to reduce
411 // cache corruption.
412 nsCacheService::GlobalInstance()->Shutdown();
413 } else if (!strcmp("profile-do-change", topic)) {
414 // profile after change
415 mHaveProfile = true;
416 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
417 ReadPrefs(branch);
418 nsCacheService::OnProfileChanged();
420 } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
422 // ignore pref changes until we're done switch profiles
423 if (!mHaveProfile)
424 return NS_OK;
426 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
427 if (NS_FAILED(rv))
428 return rv;
430 // which preference changed?
431 if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
433 rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
434 &mDiskCacheEnabled);
435 if (NS_FAILED(rv))
436 return rv;
437 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
439 } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
441 int32_t capacity = 0;
442 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
443 if (NS_FAILED(rv))
444 return rv;
445 mDiskCacheCapacity = std::max(0, capacity);
446 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
448 // Update the cache capacity when smart sizing is turned on/off
449 } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
450 // Is the update because smartsizing was turned on, or off?
451 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
452 &mSmartSizeEnabled);
453 if (NS_FAILED(rv))
454 return rv;
455 int32_t newCapacity = 0;
456 if (mSmartSizeEnabled) {
457 nsCacheService::SetDiskSmartSize();
458 } else {
459 // Smart sizing switched off: use user specified size
460 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
461 if (NS_FAILED(rv))
462 return rv;
463 mDiskCacheCapacity = std::max(0, newCapacity);
464 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
465 }
466 } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
467 rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
468 &mShouldUseOldMaxSmartSize);
469 if (NS_FAILED(rv))
470 return rv;
471 } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
472 int32_t newMaxSize;
473 rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
474 &newMaxSize);
475 if (NS_FAILED(rv))
476 return rv;
478 mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
479 nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
481 #if 0
482 } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
483 // XXX We probaby don't want to respond to this pref except after
484 // XXX profile changes. Ideally, there should be somekind of user
485 // XXX notification that the pref change won't take effect until
486 // XXX the next time the profile changes (browser launch)
487 #endif
488 } else
490 // which preference changed?
491 if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
493 rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
494 &mOfflineCacheEnabled);
495 if (NS_FAILED(rv)) return rv;
496 nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
498 } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
500 int32_t capacity = 0;
501 rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
502 if (NS_FAILED(rv)) return rv;
503 mOfflineCacheCapacity = std::max(0, capacity);
504 nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
505 #if 0
506 } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
507 // XXX We probaby don't want to respond to this pref except after
508 // XXX profile changes. Ideally, there should be some kind of user
509 // XXX notification that the pref change won't take effect until
510 // XXX the next time the profile changes (browser launch)
511 #endif
512 } else
514 if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
516 rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
517 &mMemoryCacheEnabled);
518 if (NS_FAILED(rv))
519 return rv;
520 nsCacheService::SetMemoryCache();
522 } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
524 mMemoryCacheCapacity = -1;
525 (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
526 &mMemoryCacheCapacity);
527 nsCacheService::SetMemoryCache();
528 } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
529 int32_t newMaxSize;
530 rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
531 &newMaxSize);
532 if (NS_FAILED(rv))
533 return rv;
535 mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
536 nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
537 } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
538 mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
539 (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
540 &mCacheCompressionLevel);
541 mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
542 mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
543 } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
544 rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
545 &mSanitizeOnShutdown);
546 if (NS_FAILED(rv))
547 return rv;
548 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
549 } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
550 rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
551 &mClearCacheOnShutdown);
552 if (NS_FAILED(rv))
553 return rv;
554 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
555 }
556 } else if (!strcmp("last-pb-context-exited", topic)) {
557 nsCacheService::LeavePrivateBrowsing();
558 }
560 return NS_OK;
561 }
563 // Returns default ("smart") size (in KB) of cache, given available disk space
564 // (also in KB)
565 static uint32_t
566 SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
567 {
568 uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
570 if (availKB > 100 * 1024 * 1024)
571 return maxSize; // skip computing if we're over 100 GB
573 // Grow/shrink in 10 MB units, deliberately, so that in the common case we
574 // don't shrink cache and evict items every time we startup (it's important
575 // that we don't slow down startup benchmarks).
576 uint32_t sz10MBs = 0;
577 uint32_t avail10MBs = availKB / (1024*10);
579 // .5% of space above 25 GB
580 if (avail10MBs > 2500) {
581 sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
582 avail10MBs = 2500;
583 }
584 // 1% of space between 7GB -> 25 GB
585 if (avail10MBs > 700) {
586 sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
587 avail10MBs = 700;
588 }
589 // 5% of space between 500 MB -> 7 GB
590 if (avail10MBs > 50) {
591 sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
592 avail10MBs = 50;
593 }
595 #ifdef ANDROID
596 // On Android, smaller/older devices may have very little storage and
597 // device owners may be sensitive to storage footprint: Use a smaller
598 // percentage of available space and a smaller minimum.
600 // 20% of space up to 500 MB (10 MB min)
601 sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
602 #else
603 // 40% of space up to 500 MB (50 MB min)
604 sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
605 #endif
607 return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
608 }
610 /* Computes our best guess for the default size of the user's disk cache,
611 * based on the amount of space they have free on their hard drive.
612 * We use a tiered scheme: the more space available,
613 * the larger the disk cache will be. However, we do not want
614 * to enable the disk cache to grow to an unbounded size, so the larger the
615 * user's available space is, the smaller of a percentage we take. We set a
616 * lower bound of 50MB and an upper bound of 1GB.
617 *
618 *@param: None.
619 *@return: The size that the user's disk cache should default to, in kBytes.
620 */
621 uint32_t
622 nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
623 uint32_t currentSize,
624 bool shouldUseOldMaxSmartSize)
625 {
626 // Check for free space on device where cache directory lives
627 nsresult rv;
628 nsCOMPtr<nsIFile>
629 cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
630 if (NS_FAILED(rv) || !cacheDirectory)
631 return DEFAULT_CACHE_SIZE;
632 rv = cacheDirectory->InitWithPath(cachePath);
633 if (NS_FAILED(rv))
634 return DEFAULT_CACHE_SIZE;
635 int64_t bytesAvailable;
636 rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
637 if (NS_FAILED(rv))
638 return DEFAULT_CACHE_SIZE;
640 return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
641 currentSize),
642 shouldUseOldMaxSmartSize);
643 }
645 /* Determine if we are permitted to dynamically size the user's disk cache based
646 * on their disk space available. We may do this so long as the pref
647 * smart_size.enabled is true.
648 */
649 bool
650 nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
651 firstRun)
652 {
653 nsresult rv;
654 if (firstRun) {
655 // check if user has set cache size in the past
656 bool userSet;
657 rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
658 if (NS_FAILED(rv)) userSet = true;
659 if (userSet) {
660 int32_t oldCapacity;
661 // If user explicitly set cache size to be smaller than old default
662 // of 50 MB, then keep user's value. Otherwise use smart sizing.
663 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
664 if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
665 mSmartSizeEnabled = false;
666 branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
667 mSmartSizeEnabled);
668 return mSmartSizeEnabled;
669 }
670 }
671 // Set manual setting to MAX cache size as starting val for any
672 // adjustment by user: (bug 559942 comment 65)
673 int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
674 branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
675 }
677 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
678 &mSmartSizeEnabled);
679 if (NS_FAILED(rv))
680 mSmartSizeEnabled = false;
681 return mSmartSizeEnabled;
682 }
685 nsresult
686 nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
687 {
688 nsresult rv = NS_OK;
690 // read disk cache device prefs
691 mDiskCacheEnabled = true; // presume disk cache is enabled
692 (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
694 mDiskCacheCapacity = DISK_CACHE_CAPACITY;
695 (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
696 mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
698 (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
699 &mDiskCacheMaxEntrySize);
700 mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
702 (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
703 NS_GET_IID(nsIFile),
704 getter_AddRefs(mDiskCacheParentDirectory));
706 (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
707 &mShouldUseOldMaxSmartSize);
709 if (!mDiskCacheParentDirectory) {
710 nsCOMPtr<nsIFile> directory;
712 // try to get the disk cache parent directory
713 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
714 getter_AddRefs(directory));
715 if (NS_FAILED(rv)) {
716 // try to get the profile directory (there may not be a profile yet)
717 nsCOMPtr<nsIFile> profDir;
718 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
719 getter_AddRefs(profDir));
720 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
721 getter_AddRefs(directory));
722 if (!directory)
723 directory = profDir;
724 else if (profDir) {
725 nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
726 "Cache");
727 }
728 }
729 // use file cache in build tree only if asked, to avoid cache dir litter
730 if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
731 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
732 getter_AddRefs(directory));
733 }
734 if (directory)
735 mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
736 }
737 if (mDiskCacheParentDirectory) {
738 bool firstSmartSizeRun;
739 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
740 &firstSmartSizeRun);
741 if (NS_FAILED(rv))
742 firstSmartSizeRun = false;
743 if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
744 // Avoid evictions: use previous cache size until smart size event
745 // updates mDiskCacheCapacity
746 rv = branch->GetIntPref(firstSmartSizeRun ?
747 DISK_CACHE_CAPACITY_PREF :
748 DISK_CACHE_SMART_SIZE_PREF,
749 &mDiskCacheCapacity);
750 if (NS_FAILED(rv))
751 mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
752 }
754 if (firstSmartSizeRun) {
755 // It is no longer our first run
756 rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
757 false);
758 if (NS_FAILED(rv))
759 NS_WARNING("Failed setting first_run pref in ReadPrefs.");
760 }
761 }
763 // read offline cache device prefs
764 mOfflineCacheEnabled = true; // presume offline cache is enabled
765 (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
766 &mOfflineCacheEnabled);
768 mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
769 (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
770 &mOfflineCacheCapacity);
771 mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
773 (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
774 NS_GET_IID(nsIFile),
775 getter_AddRefs(mOfflineCacheParentDirectory));
777 if (!mOfflineCacheParentDirectory) {
778 nsCOMPtr<nsIFile> directory;
780 // try to get the offline cache parent directory
781 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
782 getter_AddRefs(directory));
783 if (NS_FAILED(rv)) {
784 // try to get the profile directory (there may not be a profile yet)
785 nsCOMPtr<nsIFile> profDir;
786 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
787 getter_AddRefs(profDir));
788 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
789 getter_AddRefs(directory));
790 if (!directory)
791 directory = profDir;
792 else if (profDir) {
793 nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
794 "OfflineCache");
795 }
796 }
797 #if DEBUG
798 if (!directory) {
799 // use current process directory during development
800 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
801 getter_AddRefs(directory));
802 }
803 #endif
804 if (directory)
805 mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
806 }
808 // read memory cache device prefs
809 (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
811 mMemoryCacheCapacity = -1;
812 (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
813 &mMemoryCacheCapacity);
815 (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
816 &mMemoryCacheMaxEntrySize);
817 mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
819 // read cache compression level pref
820 mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
821 (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
822 &mCacheCompressionLevel);
823 mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
824 mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
826 // read cache shutdown sanitization prefs
827 (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
828 &mSanitizeOnShutdown);
829 (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
830 &mClearCacheOnShutdown);
832 return rv;
833 }
835 nsresult
836 nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
837 {
838 if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
839 return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
840 }
842 nsresult
843 nsCacheService::SyncWithCacheIOThread()
844 {
845 gService->mLock.AssertCurrentThreadOwns();
846 if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
848 nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
850 // dispatch event - it will notify the monitor when it's done
851 nsresult rv =
852 gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
853 if (NS_FAILED(rv)) {
854 NS_WARNING("Failed dispatching block-event");
855 return NS_ERROR_UNEXPECTED;
856 }
858 // wait until notified, then return
859 rv = gService->mCondVar.Wait();
861 return rv;
862 }
865 bool
866 nsCacheProfilePrefObserver::DiskCacheEnabled()
867 {
868 if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
869 return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
870 }
873 bool
874 nsCacheProfilePrefObserver::OfflineCacheEnabled()
875 {
876 if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
877 return false;
879 return mOfflineCacheEnabled;
880 }
883 bool
884 nsCacheProfilePrefObserver::MemoryCacheEnabled()
885 {
886 if (mMemoryCacheCapacity == 0) return false;
887 return mMemoryCacheEnabled;
888 }
891 /**
892 * MemoryCacheCapacity
893 *
894 * If the browser.cache.memory.capacity preference is positive, we use that
895 * value for the amount of memory available for the cache.
896 *
897 * If browser.cache.memory.capacity is zero, the memory cache is disabled.
898 *
899 * If browser.cache.memory.capacity is negative or not present, we use a
900 * formula that grows less than linearly with the amount of system memory,
901 * with an upper limit on the cache size. No matter how much physical RAM is
902 * present, the default cache size would not exceed 32 MB. This maximum would
903 * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
904 *
905 * RAM Cache
906 * --- -----
907 * 32 Mb 2 Mb
908 * 64 Mb 4 Mb
909 * 128 Mb 6 Mb
910 * 256 Mb 10 Mb
911 * 512 Mb 14 Mb
912 * 1024 Mb 18 Mb
913 * 2048 Mb 24 Mb
914 * 4096 Mb 30 Mb
915 *
916 * The equation for this is (for cache size C and memory size K (kbytes)):
917 * x = log2(K) - 14
918 * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
919 * if (C > 32) C = 32
920 */
922 int32_t
923 nsCacheProfilePrefObserver::MemoryCacheCapacity()
924 {
925 int32_t capacity = mMemoryCacheCapacity;
926 if (capacity >= 0) {
927 CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
928 return capacity;
929 }
931 static uint64_t bytes = PR_GetPhysicalMemorySize();
932 CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
934 // If getting the physical memory failed, arbitrarily assume
935 // 32 MB of RAM. We use a low default to have a reasonable
936 // size on all the devices we support.
937 if (bytes == 0)
938 bytes = 32 * 1024 * 1024;
940 // Conversion from unsigned int64_t to double doesn't work on all platforms.
941 // We need to truncate the value at INT64_MAX to make sure we don't
942 // overflow.
943 if (bytes > INT64_MAX)
944 bytes = INT64_MAX;
946 uint64_t kbytes = bytes >> 10;
948 double kBytesD = double(kbytes);
950 double x = log(kBytesD)/log(2.0) - 14;
951 if (x > 0) {
952 capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
953 if (capacity > 32)
954 capacity = 32;
955 capacity *= 1024;
956 } else {
957 capacity = 0;
958 }
960 return capacity;
961 }
963 int32_t
964 nsCacheProfilePrefObserver::CacheCompressionLevel()
965 {
966 return mCacheCompressionLevel;
967 }
969 /******************************************************************************
970 * nsProcessRequestEvent
971 *****************************************************************************/
973 class nsProcessRequestEvent : public nsRunnable {
974 public:
975 nsProcessRequestEvent(nsCacheRequest *aRequest)
976 {
977 MOZ_EVENT_TRACER_NAME_OBJECT(aRequest, aRequest->mKey.get());
978 MOZ_EVENT_TRACER_WAIT(aRequest, "net::cache::ProcessRequest");
979 mRequest = aRequest;
980 }
982 NS_IMETHOD Run()
983 {
984 nsresult rv;
986 NS_ASSERTION(mRequest->mListener,
987 "Sync OpenCacheEntry() posted to background thread!");
989 nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
990 rv = nsCacheService::gService->ProcessRequest(mRequest,
991 false,
992 nullptr);
994 // Don't delete the request if it was queued
995 if (!(mRequest->IsBlocking() &&
996 rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
997 delete mRequest;
999 return NS_OK;
1000 }
1002 protected:
1003 virtual ~nsProcessRequestEvent() {}
1005 private:
1006 nsCacheRequest *mRequest;
1007 };
1009 /******************************************************************************
1010 * nsDoomEvent
1011 *****************************************************************************/
1013 class nsDoomEvent : public nsRunnable {
1014 public:
1015 nsDoomEvent(nsCacheSession *session,
1016 const nsACString &key,
1017 nsICacheListener *listener)
1018 {
1019 mKey = *session->ClientID();
1020 mKey.Append(':');
1021 mKey.Append(key);
1022 mStoragePolicy = session->StoragePolicy();
1023 mListener = listener;
1024 mThread = do_GetCurrentThread();
1025 // We addref the listener here and release it in nsNotifyDoomListener
1026 // on the callers thread. If posting of nsNotifyDoomListener event fails
1027 // we leak the listener which is better than releasing it on a wrong
1028 // thread.
1029 NS_IF_ADDREF(mListener);
1030 }
1032 NS_IMETHOD Run()
1033 {
1034 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDOOMEVENT_RUN));
1036 bool foundActive = true;
1037 nsresult status = NS_ERROR_NOT_AVAILABLE;
1038 nsCacheEntry *entry;
1039 entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
1040 if (!entry) {
1041 bool collision = false;
1042 foundActive = false;
1043 entry = nsCacheService::gService->SearchCacheDevices(&mKey,
1044 mStoragePolicy,
1045 &collision);
1046 }
1048 if (entry) {
1049 status = NS_OK;
1050 nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
1051 }
1053 if (mListener) {
1054 mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
1055 NS_DISPATCH_NORMAL);
1056 // posted event will release the reference on the correct thread
1057 mListener = nullptr;
1058 }
1060 return NS_OK;
1061 }
1063 private:
1064 nsCString mKey;
1065 nsCacheStoragePolicy mStoragePolicy;
1066 nsICacheListener *mListener;
1067 nsCOMPtr<nsIThread> mThread;
1068 };
1070 /******************************************************************************
1071 * nsCacheService
1072 *****************************************************************************/
1073 nsCacheService * nsCacheService::gService = nullptr;
1075 NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal,
1076 nsIMemoryReporter)
1078 nsCacheService::nsCacheService()
1079 : mObserver(nullptr),
1080 mLock("nsCacheService.mLock"),
1081 mCondVar(mLock, "nsCacheService.mCondVar"),
1082 mTimeStampLock("nsCacheService.mTimeStampLock"),
1083 mInitialized(false),
1084 mClearingEntries(false),
1085 mEnableMemoryDevice(true),
1086 mEnableDiskDevice(true),
1087 mMemoryDevice(nullptr),
1088 mDiskDevice(nullptr),
1089 mOfflineDevice(nullptr),
1090 mTotalEntries(0),
1091 mCacheHits(0),
1092 mCacheMisses(0),
1093 mMaxKeyLength(0),
1094 mMaxDataSize(0),
1095 mMaxMetaSize(0),
1096 mDeactivateFailures(0),
1097 mDeactivatedUnboundEntries(0)
1098 {
1099 NS_ASSERTION(gService==nullptr, "multiple nsCacheService instances!");
1100 gService = this;
1102 // create list of cache devices
1103 PR_INIT_CLIST(&mDoomedEntries);
1104 }
1106 nsCacheService::~nsCacheService()
1107 {
1108 if (mInitialized) // Shutdown hasn't been called yet.
1109 (void) Shutdown();
1111 if (mObserver) {
1112 mObserver->Remove();
1113 NS_RELEASE(mObserver);
1114 }
1116 gService = nullptr;
1117 }
1120 nsresult
1121 nsCacheService::Init()
1122 {
1123 // Thie method must be called on the main thread because mCacheIOThread must
1124 // only be modified on the main thread.
1125 if (!NS_IsMainThread()) {
1126 NS_ERROR("nsCacheService::Init called off the main thread");
1127 return NS_ERROR_NOT_SAME_THREAD;
1128 }
1130 NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
1131 if (mInitialized)
1132 return NS_ERROR_ALREADY_INITIALIZED;
1134 if (mozilla::net::IsNeckoChild()) {
1135 return NS_ERROR_UNEXPECTED;
1136 }
1138 CACHE_LOG_INIT();
1140 nsresult rv;
1142 mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
1143 NS_ENSURE_SUCCESS(rv, rv);
1145 MOZ_EVENT_TRACER_NAME_OBJECT(nsCacheService::gService, "nsCacheService");
1147 rv = NS_NewNamedThread("Cache I/O",
1148 getter_AddRefs(mCacheIOThread));
1149 if (NS_FAILED(rv)) {
1150 NS_RUNTIMEABORT("Can't create cache IO thread");
1151 }
1153 rv = nsDeleteDir::Init();
1154 if (NS_FAILED(rv)) {
1155 NS_WARNING("Can't initialize nsDeleteDir");
1156 }
1158 // initialize hashtable for active cache entries
1159 rv = mActiveEntries.Init();
1160 if (NS_FAILED(rv)) return rv;
1162 // create profile/preference observer
1163 if (!mObserver) {
1164 mObserver = new nsCacheProfilePrefObserver();
1165 NS_ADDREF(mObserver);
1166 mObserver->Install();
1167 }
1169 mEnableDiskDevice = mObserver->DiskCacheEnabled();
1170 mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
1171 mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
1173 RegisterWeakMemoryReporter(this);
1175 mInitialized = true;
1176 return NS_OK;
1177 }
1179 // static
1180 PLDHashOperator
1181 nsCacheService::ShutdownCustomCacheDeviceEnum(const nsAString& aProfileDir,
1182 nsRefPtr<nsOfflineCacheDevice>& aDevice,
1183 void* aUserArg)
1184 {
1185 aDevice->Shutdown();
1186 return PL_DHASH_REMOVE;
1187 }
1189 void
1190 nsCacheService::Shutdown()
1191 {
1192 // This method must be called on the main thread because mCacheIOThread must
1193 // only be modified on the main thread.
1194 if (!NS_IsMainThread()) {
1195 NS_RUNTIMEABORT("nsCacheService::Shutdown called off the main thread");
1196 }
1198 nsCOMPtr<nsIThread> cacheIOThread;
1199 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
1201 bool shouldSanitize = false;
1202 nsCOMPtr<nsIFile> parentDir;
1204 {
1205 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
1206 NS_ASSERTION(mInitialized,
1207 "can't shutdown nsCacheService unless it has been initialized.");
1208 if (!mInitialized)
1209 return;
1211 mClearingEntries = true;
1212 DoomActiveEntries(nullptr);
1213 }
1215 CloseAllStreams();
1217 UnregisterWeakMemoryReporter(this);
1219 {
1220 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
1221 NS_ASSERTION(mInitialized, "Bad state");
1223 mInitialized = false;
1225 // Clear entries
1226 ClearDoomList();
1228 if (mSmartSizeTimer) {
1229 mSmartSizeTimer->Cancel();
1230 mSmartSizeTimer = nullptr;
1231 }
1233 // Make sure to wait for any pending cache-operations before
1234 // proceeding with destructive actions (bug #620660)
1235 (void) SyncWithCacheIOThread();
1237 // obtain the disk cache directory in case we need to sanitize it
1238 parentDir = mObserver->DiskCacheParentDirectory();
1239 shouldSanitize = mObserver->SanitizeAtShutdown();
1241 // deallocate memory and disk caches
1242 delete mMemoryDevice;
1243 mMemoryDevice = nullptr;
1245 delete mDiskDevice;
1246 mDiskDevice = nullptr;
1248 if (mOfflineDevice)
1249 mOfflineDevice->Shutdown();
1251 NS_IF_RELEASE(mOfflineDevice);
1253 mCustomOfflineDevices.Enumerate(&nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
1255 #ifdef PR_LOGGING
1256 LogCacheStatistics();
1257 #endif
1259 mClearingEntries = false;
1260 mCacheIOThread.swap(cacheIOThread);
1261 }
1263 if (cacheIOThread)
1264 nsShutdownThread::BlockingShutdown(cacheIOThread);
1266 if (shouldSanitize) {
1267 nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
1268 if (NS_SUCCEEDED(rv)) {
1269 bool exists;
1270 if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
1271 nsDeleteDir::DeleteDir(parentDir, false);
1272 }
1273 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
1274 nsDeleteDir::Shutdown(shouldSanitize);
1275 } else {
1276 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
1277 nsDeleteDir::Shutdown(shouldSanitize);
1278 }
1279 }
1282 nsresult
1283 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
1284 {
1285 nsresult rv;
1287 if (aOuter != nullptr)
1288 return NS_ERROR_NO_AGGREGATION;
1290 nsCacheService * cacheService = new nsCacheService();
1291 if (cacheService == nullptr)
1292 return NS_ERROR_OUT_OF_MEMORY;
1294 NS_ADDREF(cacheService);
1295 rv = cacheService->Init();
1296 if (NS_SUCCEEDED(rv)) {
1297 rv = cacheService->QueryInterface(aIID, aResult);
1298 }
1299 NS_RELEASE(cacheService);
1300 return rv;
1301 }
1304 NS_IMETHODIMP
1305 nsCacheService::CreateSession(const char * clientID,
1306 nsCacheStoragePolicy storagePolicy,
1307 bool streamBased,
1308 nsICacheSession **result)
1309 {
1310 *result = nullptr;
1312 if (this == nullptr) return NS_ERROR_NOT_AVAILABLE;
1314 nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
1315 if (!session) return NS_ERROR_OUT_OF_MEMORY;
1317 NS_ADDREF(*result = session);
1319 return NS_OK;
1320 }
1323 nsresult
1324 nsCacheService::EvictEntriesForSession(nsCacheSession * session)
1325 {
1326 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1327 return gService->EvictEntriesForClient(session->ClientID()->get(),
1328 session->StoragePolicy());
1329 }
1331 namespace {
1333 class EvictionNotifierRunnable : public nsRunnable
1334 {
1335 public:
1336 EvictionNotifierRunnable(nsISupports* aSubject)
1337 : mSubject(aSubject)
1338 { }
1340 NS_DECL_NSIRUNNABLE
1342 private:
1343 nsCOMPtr<nsISupports> mSubject;
1344 };
1346 NS_IMETHODIMP
1347 EvictionNotifierRunnable::Run()
1348 {
1349 nsCOMPtr<nsIObserverService> obsSvc =
1350 mozilla::services::GetObserverService();
1351 if (obsSvc) {
1352 obsSvc->NotifyObservers(mSubject,
1353 NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
1354 nullptr);
1355 }
1356 return NS_OK;
1357 }
1359 } // anonymous namespace
1361 nsresult
1362 nsCacheService::EvictEntriesForClient(const char * clientID,
1363 nsCacheStoragePolicy storagePolicy)
1364 {
1365 nsRefPtr<EvictionNotifierRunnable> r =
1366 new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
1367 NS_DispatchToMainThread(r);
1369 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
1370 nsresult res = NS_OK;
1372 if (storagePolicy == nsICache::STORE_ANYWHERE ||
1373 storagePolicy == nsICache::STORE_ON_DISK) {
1375 if (mEnableDiskDevice) {
1376 nsresult rv = NS_OK;
1377 if (!mDiskDevice)
1378 rv = CreateDiskDevice();
1379 if (mDiskDevice)
1380 rv = mDiskDevice->EvictEntries(clientID);
1381 if (NS_FAILED(rv))
1382 res = rv;
1383 }
1384 }
1386 // Only clear the offline cache if it has been specifically asked for.
1387 if (storagePolicy == nsICache::STORE_OFFLINE) {
1388 if (mEnableOfflineDevice) {
1389 nsresult rv = NS_OK;
1390 if (!mOfflineDevice)
1391 rv = CreateOfflineDevice();
1392 if (mOfflineDevice)
1393 rv = mOfflineDevice->EvictEntries(clientID);
1394 if (NS_FAILED(rv))
1395 res = rv;
1396 }
1397 }
1399 if (storagePolicy == nsICache::STORE_ANYWHERE ||
1400 storagePolicy == nsICache::STORE_IN_MEMORY) {
1401 // If there is no memory device, there is no need to evict it...
1402 if (mMemoryDevice) {
1403 nsresult rv = mMemoryDevice->EvictEntries(clientID);
1404 if (NS_FAILED(rv))
1405 res = rv;
1406 }
1407 }
1409 return res;
1410 }
1413 nsresult
1414 nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
1415 bool * result)
1416 {
1417 if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
1418 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
1420 *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
1421 return NS_OK;
1422 }
1425 nsresult
1426 nsCacheService::DoomEntry(nsCacheSession *session,
1427 const nsACString &key,
1428 nsICacheListener *listener)
1429 {
1430 CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
1431 session, PromiseFlatCString(key).get()));
1432 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1434 if (!gService->mInitialized)
1435 return NS_ERROR_NOT_INITIALIZED;
1437 return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
1438 }
1441 bool
1442 nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
1443 {
1444 if (gService->mEnableMemoryDevice &&
1445 (storagePolicy == nsICache::STORE_ANYWHERE ||
1446 storagePolicy == nsICache::STORE_IN_MEMORY)) {
1447 return true;
1448 }
1449 if (gService->mEnableDiskDevice &&
1450 (storagePolicy == nsICache::STORE_ANYWHERE ||
1451 storagePolicy == nsICache::STORE_ON_DISK)) {
1452 return true;
1453 }
1454 if (gService->mEnableOfflineDevice &&
1455 storagePolicy == nsICache::STORE_OFFLINE) {
1456 return true;
1457 }
1459 return false;
1460 }
1462 NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
1463 {
1464 NS_ENSURE_ARG_POINTER(visitor);
1466 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
1468 if (!(mEnableDiskDevice || mEnableMemoryDevice))
1469 return NS_ERROR_NOT_AVAILABLE;
1471 // XXX record the fact that a visitation is in progress,
1472 // XXX i.e. keep list of visitors in progress.
1474 nsresult rv = NS_OK;
1475 // If there is no memory device, there are then also no entries to visit...
1476 if (mMemoryDevice) {
1477 rv = mMemoryDevice->Visit(visitor);
1478 if (NS_FAILED(rv)) return rv;
1479 }
1481 if (mEnableDiskDevice) {
1482 if (!mDiskDevice) {
1483 rv = CreateDiskDevice();
1484 if (NS_FAILED(rv)) return rv;
1485 }
1486 rv = mDiskDevice->Visit(visitor);
1487 if (NS_FAILED(rv)) return rv;
1488 }
1490 if (mEnableOfflineDevice) {
1491 if (!mOfflineDevice) {
1492 rv = CreateOfflineDevice();
1493 if (NS_FAILED(rv)) return rv;
1494 }
1495 rv = mOfflineDevice->Visit(visitor);
1496 if (NS_FAILED(rv)) return rv;
1497 }
1499 // XXX notify any shutdown process that visitation is complete for THIS visitor.
1500 // XXX keep queue of visitors
1502 return NS_OK;
1503 }
1505 void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification()
1506 {
1507 MOZ_ASSERT(NS_IsMainThread());
1508 nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
1509 if (obsvc) {
1510 obsvc->NotifyObservers(nullptr,
1511 "network-clear-cache-stored-anywhere",
1512 nullptr);
1513 }
1514 }
1516 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
1517 {
1518 if (storagePolicy == nsICache::STORE_ANYWHERE) {
1519 // if not called on main thread, dispatch the notification to the main thread to notify observers
1520 if (!NS_IsMainThread()) {
1521 nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(this,
1522 &nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
1523 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
1524 } else {
1525 // else you're already on main thread - notify observers
1526 FireClearNetworkCacheStoredAnywhereNotification();
1527 }
1528 }
1530 NS_IMETHODIMP r;
1531 r = EvictEntriesForClient(nullptr, storagePolicy);
1533 // XXX: Bloody hack until we get this notifier in FF14.0:
1534 // https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsICacheListener#onCacheEntryDoomed%28%29
1535 if (storagePolicy == nsICache::STORE_ANYWHERE &&
1536 NS_IsMainThread() && gService && gService->mInitialized) {
1537 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
1538 gService->mClearingEntries = true;
1539 gService->DoomActiveEntries(nullptr);
1540 gService->ClearDoomList();
1541 (void) SyncWithCacheIOThread();
1542 gService->mClearingEntries = false;
1543 }
1544 return r;
1545 }
1547 NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
1548 {
1549 NS_ENSURE_ARG_POINTER(aCacheIOTarget);
1551 // Because mCacheIOThread can only be changed on the main thread, it can be
1552 // read from the main thread without the lock. This is useful to prevent
1553 // blocking the main thread on other cache operations.
1554 if (!NS_IsMainThread()) {
1555 Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
1556 }
1558 nsresult rv;
1559 if (mCacheIOThread) {
1560 NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
1561 rv = NS_OK;
1562 } else {
1563 *aCacheIOTarget = nullptr;
1564 rv = NS_ERROR_NOT_AVAILABLE;
1565 }
1567 if (!NS_IsMainThread()) {
1568 Unlock();
1569 }
1571 return rv;
1572 }
1574 /* nsICacheServiceInternal
1575 * readonly attribute double lockHeldTime;
1576 */
1577 NS_IMETHODIMP nsCacheService::GetLockHeldTime(double *aLockHeldTime)
1578 {
1579 MutexAutoLock lock(mTimeStampLock);
1581 if (mLockAcquiredTimeStamp.IsNull()) {
1582 *aLockHeldTime = 0.0;
1583 }
1584 else {
1585 *aLockHeldTime =
1586 (TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
1587 }
1589 return NS_OK;
1590 }
1592 /**
1593 * Internal Methods
1594 */
1595 nsresult
1596 nsCacheService::CreateDiskDevice()
1597 {
1598 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1599 if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
1600 if (mDiskDevice) return NS_OK;
1602 mDiskDevice = new nsDiskCacheDevice;
1603 if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
1605 // set the preferences
1606 mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
1607 mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
1608 mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
1610 nsresult rv = mDiskDevice->Init();
1611 if (NS_FAILED(rv)) {
1612 #if DEBUG
1613 printf("###\n");
1614 printf("### mDiskDevice->Init() failed (0x%.8x)\n",
1615 static_cast<uint32_t>(rv));
1616 printf("### - disabling disk cache for this session.\n");
1617 printf("###\n");
1618 #endif
1619 mEnableDiskDevice = false;
1620 delete mDiskDevice;
1621 mDiskDevice = nullptr;
1622 return rv;
1623 }
1625 Telemetry::Accumulate(Telemetry::DISK_CACHE_SMART_SIZE_USING_OLD_MAX,
1626 mObserver->ShouldUseOldMaxSmartSize());
1628 NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
1630 // Disk device is usually created during the startup. Delay smart size
1631 // calculation to avoid possible massive IO caused by eviction of entries
1632 // in case the new smart size is smaller than current cache usage.
1633 mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1634 if (NS_SUCCEEDED(rv)) {
1635 rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
1636 1000*60*3,
1637 nsITimer::TYPE_ONE_SHOT);
1638 if (NS_FAILED(rv)) {
1639 NS_WARNING("Failed to post smart size timer");
1640 mSmartSizeTimer = nullptr;
1641 }
1642 } else {
1643 NS_WARNING("Can't create smart size timer");
1644 }
1645 // Ignore state of the timer and return success since the purpose of the
1646 // method (create the disk-device) has been fulfilled
1648 return NS_OK;
1649 }
1651 // Runnable sent from cache thread to main thread
1652 class nsDisableOldMaxSmartSizePrefEvent: public nsRunnable
1653 {
1654 public:
1655 nsDisableOldMaxSmartSizePrefEvent() {}
1657 NS_IMETHOD Run()
1658 {
1659 // Main thread may have already called nsCacheService::Shutdown
1660 if (!nsCacheService::IsInitialized())
1661 return NS_ERROR_NOT_AVAILABLE;
1663 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
1664 if (!branch) {
1665 return NS_ERROR_NOT_AVAILABLE;
1666 }
1668 nsresult rv = branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
1669 if (NS_FAILED(rv)) {
1670 NS_WARNING("Failed to disable old max smart size");
1671 return rv;
1672 }
1674 // It is safe to call SetDiskSmartSize_Locked() without holding the lock
1675 // when we are on main thread and nsCacheService is initialized.
1676 nsCacheService::gService->SetDiskSmartSize_Locked();
1678 if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch, false)) {
1679 rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
1680 if (NS_FAILED(rv)) {
1681 NS_WARNING("Failed to set cache capacity pref");
1682 }
1683 }
1685 return NS_OK;
1686 }
1687 };
1689 void
1690 nsCacheService::MarkStartingFresh()
1691 {
1692 if (!gService->mObserver->ShouldUseOldMaxSmartSize()) {
1693 // Already using new max, nothing to do here
1694 return;
1695 }
1697 gService->mObserver->SetUseNewMaxSmartSize(true);
1699 // We always dispatch an event here because we don't want to deal with lock
1700 // reentrance issues.
1701 NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
1702 }
1704 nsresult
1705 nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
1706 {
1707 if (!mOfflineDevice) {
1708 nsresult rv = CreateOfflineDevice();
1709 NS_ENSURE_SUCCESS(rv, rv);
1710 }
1712 NS_ADDREF(*aDevice = mOfflineDevice);
1713 return NS_OK;
1714 }
1716 nsresult
1717 nsCacheService::GetCustomOfflineDevice(nsIFile *aProfileDir,
1718 int32_t aQuota,
1719 nsOfflineCacheDevice **aDevice)
1720 {
1721 nsresult rv;
1723 nsAutoString profilePath;
1724 rv = aProfileDir->GetPath(profilePath);
1725 NS_ENSURE_SUCCESS(rv, rv);
1727 if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
1728 rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
1729 NS_ENSURE_SUCCESS(rv, rv);
1731 (*aDevice)->SetAutoShutdown();
1732 mCustomOfflineDevices.Put(profilePath, *aDevice);
1733 }
1735 return NS_OK;
1736 }
1738 nsresult
1739 nsCacheService::CreateOfflineDevice()
1740 {
1741 CACHE_LOG_ALWAYS(("Creating default offline device"));
1743 if (mOfflineDevice) return NS_OK;
1744 if (!nsCacheService::IsInitialized()) {
1745 return NS_ERROR_NOT_AVAILABLE;
1746 }
1748 nsresult rv = CreateCustomOfflineDevice(
1749 mObserver->OfflineCacheParentDirectory(),
1750 mObserver->OfflineCacheCapacity(),
1751 &mOfflineDevice);
1752 NS_ENSURE_SUCCESS(rv, rv);
1754 return NS_OK;
1755 }
1757 nsresult
1758 nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
1759 int32_t aQuota,
1760 nsOfflineCacheDevice **aDevice)
1761 {
1762 NS_ENSURE_ARG(aProfileDir);
1764 #if defined(PR_LOGGING)
1765 nsAutoCString profilePath;
1766 aProfileDir->GetNativePath(profilePath);
1767 CACHE_LOG_ALWAYS(("Creating custom offline device, %s, %d",
1768 profilePath.BeginReading(), aQuota));
1769 #endif
1771 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1772 if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
1774 *aDevice = new nsOfflineCacheDevice;
1776 NS_ADDREF(*aDevice);
1778 // set the preferences
1779 (*aDevice)->SetCacheParentDirectory(aProfileDir);
1780 (*aDevice)->SetCapacity(aQuota);
1782 nsresult rv = (*aDevice)->InitWithSqlite(mStorageService);
1783 if (NS_FAILED(rv)) {
1784 CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8x)\n", rv));
1785 CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
1787 NS_RELEASE(*aDevice);
1788 }
1789 return rv;
1790 }
1792 nsresult
1793 nsCacheService::CreateMemoryDevice()
1794 {
1795 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1796 if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
1797 if (mMemoryDevice) return NS_OK;
1799 mMemoryDevice = new nsMemoryCacheDevice;
1800 if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
1802 // set preference
1803 int32_t capacity = mObserver->MemoryCacheCapacity();
1804 CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
1805 mMemoryDevice->SetCapacity(capacity);
1806 mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
1808 nsresult rv = mMemoryDevice->Init();
1809 if (NS_FAILED(rv)) {
1810 NS_WARNING("Initialization of Memory Cache failed.");
1811 delete mMemoryDevice;
1812 mMemoryDevice = nullptr;
1813 }
1815 return rv;
1816 }
1818 nsresult
1819 nsCacheService::RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice)
1820 {
1821 nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
1822 if (!profileDir)
1823 return NS_ERROR_UNEXPECTED;
1825 nsAutoString profilePath;
1826 nsresult rv = profileDir->GetPath(profilePath);
1827 NS_ENSURE_SUCCESS(rv, rv);
1829 mCustomOfflineDevices.Remove(profilePath);
1830 return NS_OK;
1831 }
1833 nsresult
1834 nsCacheService::CreateRequest(nsCacheSession * session,
1835 const nsACString & clientKey,
1836 nsCacheAccessMode accessRequested,
1837 bool blockingMode,
1838 nsICacheListener * listener,
1839 nsCacheRequest ** request)
1840 {
1841 NS_ASSERTION(request, "CreateRequest: request is null");
1843 nsAutoCString key(*session->ClientID());
1844 key.Append(':');
1845 key.Append(clientKey);
1847 if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
1849 // create request
1850 *request = new nsCacheRequest(key, listener, accessRequested,
1851 blockingMode, session);
1853 if (!listener) return NS_OK; // we're sync, we're done.
1855 // get the request's thread
1856 (*request)->mThread = do_GetCurrentThread();
1858 return NS_OK;
1859 }
1862 class nsCacheListenerEvent : public nsRunnable
1863 {
1864 public:
1865 nsCacheListenerEvent(nsICacheListener *listener,
1866 nsICacheEntryDescriptor *descriptor,
1867 nsCacheAccessMode accessGranted,
1868 nsresult status)
1869 : mListener(listener) // transfers reference
1870 , mDescriptor(descriptor) // transfers reference (may be null)
1871 , mAccessGranted(accessGranted)
1872 , mStatus(status)
1873 {}
1875 NS_IMETHOD Run()
1876 {
1877 mozilla::eventtracer::AutoEventTracer tracer(
1878 static_cast<nsIRunnable*>(this),
1879 eventtracer::eExec,
1880 eventtracer::eDone,
1881 "net::cache::OnCacheEntryAvailable");
1883 mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
1885 NS_RELEASE(mListener);
1886 NS_IF_RELEASE(mDescriptor);
1887 return NS_OK;
1888 }
1890 private:
1891 // We explicitly leak mListener or mDescriptor if Run is not called
1892 // because otherwise we cannot guarantee that they are destroyed on
1893 // the right thread.
1895 nsICacheListener *mListener;
1896 nsICacheEntryDescriptor *mDescriptor;
1897 nsCacheAccessMode mAccessGranted;
1898 nsresult mStatus;
1899 };
1902 nsresult
1903 nsCacheService::NotifyListener(nsCacheRequest * request,
1904 nsICacheEntryDescriptor * descriptor,
1905 nsCacheAccessMode accessGranted,
1906 nsresult status)
1907 {
1908 NS_ASSERTION(request->mThread, "no thread set in async request!");
1910 // Swap ownership, and release listener on target thread...
1911 nsICacheListener *listener = request->mListener;
1912 request->mListener = nullptr;
1914 nsCOMPtr<nsIRunnable> ev =
1915 new nsCacheListenerEvent(listener, descriptor,
1916 accessGranted, status);
1917 if (!ev) {
1918 // Better to leak listener and descriptor if we fail because we don't
1919 // want to destroy them inside the cache service lock or on potentially
1920 // the wrong thread.
1921 return NS_ERROR_OUT_OF_MEMORY;
1922 }
1924 MOZ_EVENT_TRACER_NAME_OBJECT(ev.get(), request->mKey.get());
1925 MOZ_EVENT_TRACER_WAIT(ev.get(), "net::cache::OnCacheEntryAvailable");
1926 return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1927 }
1930 nsresult
1931 nsCacheService::ProcessRequest(nsCacheRequest * request,
1932 bool calledFromOpenCacheEntry,
1933 nsICacheEntryDescriptor ** result)
1934 {
1935 mozilla::eventtracer::AutoEventTracer tracer(
1936 request,
1937 eventtracer::eExec,
1938 eventtracer::eDone,
1939 "net::cache::ProcessRequest");
1941 // !!! must be called with mLock held !!!
1942 nsresult rv;
1943 nsCacheEntry * entry = nullptr;
1944 nsCacheEntry * doomedEntry = nullptr;
1945 nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1946 if (result) *result = nullptr;
1948 while(1) { // Activate entry loop
1949 rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
1950 if (NS_FAILED(rv)) break;
1952 while(1) { // Request Access loop
1953 NS_ASSERTION(entry, "no entry in Request Access loop!");
1954 // entry->RequestAccess queues request on entry
1955 rv = entry->RequestAccess(request, &accessGranted);
1956 if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
1958 if (request->IsBlocking()) {
1959 if (request->mListener) {
1960 // async exits - validate, doom, or close will resume
1961 return rv;
1962 }
1964 // XXX this is probably wrong...
1965 Unlock();
1966 rv = request->WaitForValidation();
1967 Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
1968 }
1970 PR_REMOVE_AND_INIT_LINK(request);
1971 if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
1972 // okay, we're ready to process this request, request access again
1973 }
1974 if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
1976 if (entry->IsNotInUse()) {
1977 // this request was the last one keeping it around, so get rid of it
1978 DeactivateEntry(entry);
1979 }
1980 // loop back around to look for another entry
1981 }
1983 if (NS_SUCCEEDED(rv) && request->mProfileDir) {
1984 // Custom cache directory has been demanded. Preset the cache device.
1985 if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
1986 // Failsafe check: this is implemented only for offline cache atm.
1987 rv = NS_ERROR_FAILURE;
1988 } else {
1989 nsRefPtr<nsOfflineCacheDevice> customCacheDevice;
1990 rv = GetCustomOfflineDevice(request->mProfileDir, -1,
1991 getter_AddRefs(customCacheDevice));
1992 if (NS_SUCCEEDED(rv))
1993 entry->SetCustomCacheDevice(customCacheDevice);
1994 }
1995 }
1997 nsICacheEntryDescriptor *descriptor = nullptr;
1999 if (NS_SUCCEEDED(rv))
2000 rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
2002 // If doomedEntry is set, ActivatEntry() doomed an existing entry and
2003 // created a new one for that cache-key. However, any pending requests
2004 // on the doomed entry were not processed and we need to do that here.
2005 // This must be done after adding the created entry to list of active
2006 // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
2007 // (see bug ##561313). It is also important to do this after creating a
2008 // descriptor for this request, or some other request may end up being
2009 // executed first for the newly created entry.
2010 // Finally, it is worth to emphasize that if doomedEntry is set,
2011 // ActivateEntry() created a new entry for the request, which will be
2012 // initialized by RequestAccess() and they both should have returned NS_OK.
2013 if (doomedEntry) {
2014 (void) ProcessPendingRequests(doomedEntry);
2015 if (doomedEntry->IsNotInUse())
2016 DeactivateEntry(doomedEntry);
2017 doomedEntry = nullptr;
2018 }
2020 if (request->mListener) { // Asynchronous
2022 if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
2023 return rv; // skip notifying listener, just return rv to caller
2025 // call listener to report error or descriptor
2026 nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
2027 if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
2028 rv = rv2; // trigger delete request
2029 }
2030 } else { // Synchronous
2031 *result = descriptor;
2032 }
2033 return rv;
2034 }
2037 nsresult
2038 nsCacheService::OpenCacheEntry(nsCacheSession * session,
2039 const nsACString & key,
2040 nsCacheAccessMode accessRequested,
2041 bool blockingMode,
2042 nsICacheListener * listener,
2043 nsICacheEntryDescriptor ** result)
2044 {
2045 CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
2046 session, PromiseFlatCString(key).get(), accessRequested,
2047 blockingMode));
2048 NS_ASSERTION(gService, "nsCacheService::gService is null.");
2049 if (result)
2050 *result = nullptr;
2052 if (!gService->mInitialized)
2053 return NS_ERROR_NOT_INITIALIZED;
2055 nsCacheRequest * request = nullptr;
2057 nsresult rv = gService->CreateRequest(session,
2058 key,
2059 accessRequested,
2060 blockingMode,
2061 listener,
2062 &request);
2063 if (NS_FAILED(rv)) return rv;
2065 CACHE_LOG_DEBUG(("Created request %p\n", request));
2067 // Process the request on the background thread if we are on the main thread
2068 // and the the request is asynchronous
2069 if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
2070 nsCOMPtr<nsIRunnable> ev =
2071 new nsProcessRequestEvent(request);
2072 rv = DispatchToCacheIOThread(ev);
2074 // delete request if we didn't post the event
2075 if (NS_FAILED(rv))
2076 delete request;
2077 }
2078 else {
2080 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
2081 rv = gService->ProcessRequest(request, true, result);
2083 // delete requests that have completed
2084 if (!(listener && blockingMode &&
2085 (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
2086 delete request;
2087 }
2089 return rv;
2090 }
2093 nsresult
2094 nsCacheService::ActivateEntry(nsCacheRequest * request,
2095 nsCacheEntry ** result,
2096 nsCacheEntry ** doomedEntry)
2097 {
2098 CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
2099 if (!mInitialized || mClearingEntries)
2100 return NS_ERROR_NOT_AVAILABLE;
2102 mozilla::eventtracer::AutoEventTracer tracer(
2103 request,
2104 eventtracer::eExec,
2105 eventtracer::eDone,
2106 "net::cache::ActivateEntry");
2108 nsresult rv = NS_OK;
2110 NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
2111 if (result) *result = nullptr;
2112 if (doomedEntry) *doomedEntry = nullptr;
2113 if ((!request) || (!result) || (!doomedEntry))
2114 return NS_ERROR_NULL_POINTER;
2116 // check if the request can be satisfied
2117 if (!mEnableMemoryDevice && !request->IsStreamBased())
2118 return NS_ERROR_FAILURE;
2119 if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
2120 return NS_ERROR_FAILURE;
2122 // search active entries (including those not bound to device)
2123 nsCacheEntry *entry = mActiveEntries.GetEntry(&(request->mKey));
2124 CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
2126 if (!entry) {
2127 // search cache devices for entry
2128 bool collision = false;
2129 entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(), &collision);
2130 CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
2131 request, entry));
2132 // When there is a hashkey collision just refuse to cache it...
2133 if (collision) return NS_ERROR_CACHE_IN_USE;
2135 if (entry) entry->MarkInitialized();
2136 } else {
2137 NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
2138 }
2140 if (entry) {
2141 ++mCacheHits;
2142 entry->Fetched();
2143 } else {
2144 ++mCacheMisses;
2145 }
2147 if (entry &&
2148 ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
2149 ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
2150 (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
2151 request->WillDoomEntriesIfExpired()))))
2153 {
2154 // this is FORCE-WRITE request or the entry has expired
2155 // we doom entry without processing pending requests, but store it in
2156 // doomedEntry which causes pending requests to be processed below
2157 rv = DoomEntry_Internal(entry, false);
2158 *doomedEntry = entry;
2159 if (NS_FAILED(rv)) {
2160 // XXX what to do? Increment FailedDooms counter?
2161 }
2162 entry = nullptr;
2163 }
2165 if (!entry) {
2166 if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
2167 // this is a READ-ONLY request
2168 rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
2169 goto error;
2170 }
2172 entry = new nsCacheEntry(request->mKey,
2173 request->IsStreamBased(),
2174 request->StoragePolicy());
2175 if (!entry)
2176 return NS_ERROR_OUT_OF_MEMORY;
2178 if (request->IsPrivate())
2179 entry->MarkPrivate();
2181 entry->Fetched();
2182 ++mTotalEntries;
2184 // XXX we could perform an early bind in some cases based on storage policy
2185 }
2187 if (!entry->IsActive()) {
2188 rv = mActiveEntries.AddEntry(entry);
2189 if (NS_FAILED(rv)) goto error;
2190 CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
2191 entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
2192 }
2193 *result = entry;
2194 return NS_OK;
2196 error:
2197 *result = nullptr;
2198 delete entry;
2199 return rv;
2200 }
2203 nsCacheEntry *
2204 nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
2205 {
2206 Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
2207 nsCacheEntry * entry = nullptr;
2209 MOZ_EVENT_TRACER_NAME_OBJECT(key, key->BeginReading());
2210 eventtracer::AutoEventTracer searchCacheDevices(
2211 key,
2212 eventtracer::eExec,
2213 eventtracer::eDone,
2214 "net::cache::SearchCacheDevices");
2216 CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
2218 *collision = false;
2219 if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
2220 // If there is no memory device, then there is nothing to search...
2221 if (mMemoryDevice) {
2222 entry = mMemoryDevice->FindEntry(key, collision);
2223 CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
2224 "collision: %d\n", key->get(), entry, collision));
2225 }
2226 }
2228 if (!entry &&
2229 ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
2231 if (mEnableDiskDevice) {
2232 if (!mDiskDevice) {
2233 nsresult rv = CreateDiskDevice();
2234 if (NS_FAILED(rv))
2235 return nullptr;
2236 }
2238 entry = mDiskDevice->FindEntry(key, collision);
2239 }
2240 }
2242 if (!entry && (policy == nsICache::STORE_OFFLINE ||
2243 (policy == nsICache::STORE_ANYWHERE &&
2244 gIOService->IsOffline()))) {
2246 if (mEnableOfflineDevice) {
2247 if (!mOfflineDevice) {
2248 nsresult rv = CreateOfflineDevice();
2249 if (NS_FAILED(rv))
2250 return nullptr;
2251 }
2253 entry = mOfflineDevice->FindEntry(key, collision);
2254 }
2255 }
2257 return entry;
2258 }
2261 nsCacheDevice *
2262 nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
2263 {
2264 nsCacheDevice * device = entry->CacheDevice();
2265 // return device if found, possibly null if the entry is doomed i.e prevent
2266 // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
2267 if (device || entry->IsDoomed()) return device;
2269 int64_t predictedDataSize = entry->PredictedDataSize();
2270 if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
2271 // this is the default
2272 if (!mDiskDevice) {
2273 (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
2274 }
2276 if (mDiskDevice) {
2277 // Bypass the cache if Content-Length says the entry will be too big
2278 if (predictedDataSize != -1 &&
2279 mDiskDevice->EntryIsTooBig(predictedDataSize)) {
2280 DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
2281 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
2282 return nullptr;
2283 }
2285 entry->MarkBinding(); // enter state of binding
2286 nsresult rv = mDiskDevice->BindEntry(entry);
2287 entry->ClearBinding(); // exit state of binding
2288 if (NS_SUCCEEDED(rv))
2289 device = mDiskDevice;
2290 }
2291 }
2293 // if we can't use mDiskDevice, try mMemoryDevice
2294 if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
2295 if (!mMemoryDevice) {
2296 (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
2297 }
2298 if (mMemoryDevice) {
2299 // Bypass the cache if Content-Length says entry will be too big
2300 if (predictedDataSize != -1 &&
2301 mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
2302 DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
2303 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
2304 return nullptr;
2305 }
2307 entry->MarkBinding(); // enter state of binding
2308 nsresult rv = mMemoryDevice->BindEntry(entry);
2309 entry->ClearBinding(); // exit state of binding
2310 if (NS_SUCCEEDED(rv))
2311 device = mMemoryDevice;
2312 }
2313 }
2315 if (!device && entry->IsStreamData() &&
2316 entry->IsAllowedOffline() && mEnableOfflineDevice) {
2317 if (!mOfflineDevice) {
2318 (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
2319 }
2321 device = entry->CustomCacheDevice()
2322 ? entry->CustomCacheDevice()
2323 : mOfflineDevice;
2325 if (device) {
2326 entry->MarkBinding();
2327 nsresult rv = device->BindEntry(entry);
2328 entry->ClearBinding();
2329 if (NS_FAILED(rv))
2330 device = nullptr;
2331 }
2332 }
2334 if (device)
2335 entry->SetCacheDevice(device);
2336 return device;
2337 }
2339 nsresult
2340 nsCacheService::DoomEntry(nsCacheEntry * entry)
2341 {
2342 return gService->DoomEntry_Internal(entry, true);
2343 }
2346 nsresult
2347 nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
2348 bool doProcessPendingRequests)
2349 {
2350 if (entry->IsDoomed()) return NS_OK;
2352 CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
2353 nsresult rv = NS_OK;
2354 entry->MarkDoomed();
2356 NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
2357 nsCacheDevice * device = entry->CacheDevice();
2358 if (device) device->DoomEntry(entry);
2360 if (entry->IsActive()) {
2361 // remove from active entries
2362 mActiveEntries.RemoveEntry(entry);
2363 CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
2364 entry->MarkInactive();
2365 }
2367 // put on doom list to wait for descriptors to close
2368 NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
2369 PR_APPEND_LINK(entry, &mDoomedEntries);
2371 // handle pending requests only if we're supposed to
2372 if (doProcessPendingRequests) {
2373 // tell pending requests to get on with their lives...
2374 rv = ProcessPendingRequests(entry);
2376 // All requests have been removed, but there may still be open descriptors
2377 if (entry->IsNotInUse()) {
2378 DeactivateEntry(entry); // tell device to get rid of it
2379 }
2380 }
2381 return rv;
2382 }
2385 void
2386 nsCacheService::OnProfileShutdown(bool cleanse)
2387 {
2388 if (!gService) return;
2389 if (!gService->mInitialized) {
2390 // The cache service has been shut down, but someone is still holding
2391 // a reference to it. Ignore this call.
2392 return;
2393 }
2394 {
2395 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
2396 gService->mClearingEntries = true;
2397 gService->DoomActiveEntries(nullptr);
2398 }
2400 gService->CloseAllStreams();
2402 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
2403 gService->ClearDoomList();
2405 // Make sure to wait for any pending cache-operations before
2406 // proceeding with destructive actions (bug #620660)
2407 (void) SyncWithCacheIOThread();
2409 if (gService->mDiskDevice && gService->mEnableDiskDevice) {
2410 if (cleanse)
2411 gService->mDiskDevice->EvictEntries(nullptr);
2413 gService->mDiskDevice->Shutdown();
2414 }
2415 gService->mEnableDiskDevice = false;
2417 if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
2418 if (cleanse)
2419 gService->mOfflineDevice->EvictEntries(nullptr);
2421 gService->mOfflineDevice->Shutdown();
2422 }
2423 gService->mCustomOfflineDevices.Enumerate(
2424 &nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
2426 gService->mEnableOfflineDevice = false;
2428 if (gService->mMemoryDevice) {
2429 // clear memory cache
2430 gService->mMemoryDevice->EvictEntries(nullptr);
2431 }
2433 gService->mClearingEntries = false;
2434 }
2437 void
2438 nsCacheService::OnProfileChanged()
2439 {
2440 if (!gService) return;
2442 CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
2444 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
2446 gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2447 gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2448 gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2450 if (gService->mDiskDevice) {
2451 gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
2452 gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
2454 // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
2455 nsresult rv = gService->mDiskDevice->Init();
2456 if (NS_FAILED(rv)) {
2457 NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
2458 gService->mEnableDiskDevice = false;
2459 // XXX delete mDiskDevice?
2460 }
2461 }
2463 if (gService->mOfflineDevice) {
2464 gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
2465 gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
2467 // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
2468 nsresult rv = gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
2469 if (NS_FAILED(rv)) {
2470 NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
2471 gService->mEnableOfflineDevice = false;
2472 // XXX delete mOfflineDevice?
2473 }
2474 }
2476 // If memoryDevice exists, reset its size to the new profile
2477 if (gService->mMemoryDevice) {
2478 if (gService->mEnableMemoryDevice) {
2479 // make sure that capacity is reset to the right value
2480 int32_t capacity = gService->mObserver->MemoryCacheCapacity();
2481 CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2482 capacity));
2483 gService->mMemoryDevice->SetCapacity(capacity);
2484 } else {
2485 // tell memory device to evict everything
2486 CACHE_LOG_DEBUG(("memory device disabled\n"));
2487 gService->mMemoryDevice->SetCapacity(0);
2488 // Don't delete memory device, because some entries may be active still...
2489 }
2490 }
2491 }
2494 void
2495 nsCacheService::SetDiskCacheEnabled(bool enabled)
2496 {
2497 if (!gService) return;
2498 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
2499 gService->mEnableDiskDevice = enabled;
2500 }
2503 void
2504 nsCacheService::SetDiskCacheCapacity(int32_t capacity)
2505 {
2506 if (!gService) return;
2507 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
2509 if (gService->mDiskDevice) {
2510 gService->mDiskDevice->SetCapacity(capacity);
2511 }
2513 gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2514 }
2516 void
2517 nsCacheService::SetDiskCacheMaxEntrySize(int32_t maxSize)
2518 {
2519 if (!gService) return;
2520 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
2522 if (gService->mDiskDevice) {
2523 gService->mDiskDevice->SetMaxEntrySize(maxSize);
2524 }
2525 }
2527 void
2528 nsCacheService::SetMemoryCacheMaxEntrySize(int32_t maxSize)
2529 {
2530 if (!gService) return;
2531 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
2533 if (gService->mMemoryDevice) {
2534 gService->mMemoryDevice->SetMaxEntrySize(maxSize);
2535 }
2536 }
2538 void
2539 nsCacheService::SetOfflineCacheEnabled(bool enabled)
2540 {
2541 if (!gService) return;
2542 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
2543 gService->mEnableOfflineDevice = enabled;
2544 }
2546 void
2547 nsCacheService::SetOfflineCacheCapacity(int32_t capacity)
2548 {
2549 if (!gService) return;
2550 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
2552 if (gService->mOfflineDevice) {
2553 gService->mOfflineDevice->SetCapacity(capacity);
2554 }
2556 gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2557 }
2560 void
2561 nsCacheService::SetMemoryCache()
2562 {
2563 if (!gService) return;
2565 CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
2567 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
2569 gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2571 if (gService->mEnableMemoryDevice) {
2572 if (gService->mMemoryDevice) {
2573 int32_t capacity = gService->mObserver->MemoryCacheCapacity();
2574 // make sure that capacity is reset to the right value
2575 CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2576 capacity));
2577 gService->mMemoryDevice->SetCapacity(capacity);
2578 }
2579 } else {
2580 if (gService->mMemoryDevice) {
2581 // tell memory device to evict everything
2582 CACHE_LOG_DEBUG(("memory device disabled\n"));
2583 gService->mMemoryDevice->SetCapacity(0);
2584 // Don't delete memory device, because some entries may be active still...
2585 }
2586 }
2587 }
2590 /******************************************************************************
2591 * static methods for nsCacheEntryDescriptor
2592 *****************************************************************************/
2593 void
2594 nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
2595 {
2596 // ask entry to remove descriptor
2597 nsCacheEntry * entry = descriptor->CacheEntry();
2598 bool doomEntry;
2599 bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
2601 if (!entry->IsValid()) {
2602 gService->ProcessPendingRequests(entry);
2603 }
2605 if (doomEntry) {
2606 gService->DoomEntry_Internal(entry, true);
2607 return;
2608 }
2610 if (!stillActive) {
2611 gService->DeactivateEntry(entry);
2612 }
2613 }
2616 nsresult
2617 nsCacheService::GetFileForEntry(nsCacheEntry * entry,
2618 nsIFile ** result)
2619 {
2620 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2621 if (!device) return NS_ERROR_UNEXPECTED;
2623 return device->GetFileForEntry(entry, result);
2624 }
2627 nsresult
2628 nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
2629 nsCacheAccessMode mode,
2630 uint32_t offset,
2631 nsIInputStream ** result)
2632 {
2633 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2634 if (!device) return NS_ERROR_UNEXPECTED;
2636 return device->OpenInputStreamForEntry(entry, mode, offset, result);
2637 }
2639 nsresult
2640 nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
2641 nsCacheAccessMode mode,
2642 uint32_t offset,
2643 nsIOutputStream ** result)
2644 {
2645 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2646 if (!device) return NS_ERROR_UNEXPECTED;
2648 return device->OpenOutputStreamForEntry(entry, mode, offset, result);
2649 }
2652 nsresult
2653 nsCacheService::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
2654 {
2655 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2656 if (!device) return NS_ERROR_UNEXPECTED;
2658 return device->OnDataSizeChange(entry, deltaSize);
2659 }
2661 void
2662 nsCacheService::LockAcquired()
2663 {
2664 MutexAutoLock lock(mTimeStampLock);
2665 mLockAcquiredTimeStamp = TimeStamp::Now();
2666 }
2668 void
2669 nsCacheService::LockReleased()
2670 {
2671 MutexAutoLock lock(mTimeStampLock);
2672 mLockAcquiredTimeStamp = TimeStamp();
2673 }
2675 void
2676 nsCacheService::Lock(mozilla::Telemetry::ID mainThreadLockerID)
2677 {
2678 mozilla::Telemetry::ID lockerID;
2679 mozilla::Telemetry::ID generalID;
2681 if (NS_IsMainThread()) {
2682 lockerID = mainThreadLockerID;
2683 generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
2684 } else {
2685 lockerID = mozilla::Telemetry::HistogramCount;
2686 generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
2687 }
2689 TimeStamp start(TimeStamp::Now());
2690 MOZ_EVENT_TRACER_WAIT(nsCacheService::gService, "net::cache::lock");
2692 gService->mLock.Lock();
2693 gService->LockAcquired();
2695 TimeStamp stop(TimeStamp::Now());
2696 MOZ_EVENT_TRACER_EXEC(nsCacheService::gService, "net::cache::lock");
2698 // Telemetry isn't thread safe on its own, but this is OK because we're
2699 // protecting it with the cache lock.
2700 if (lockerID != mozilla::Telemetry::HistogramCount) {
2701 mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
2702 }
2703 mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
2704 }
2706 void
2707 nsCacheService::Unlock()
2708 {
2709 gService->mLock.AssertCurrentThreadOwns();
2711 nsTArray<nsISupports*> doomed;
2712 doomed.SwapElements(gService->mDoomedObjects);
2714 gService->LockReleased();
2715 gService->mLock.Unlock();
2717 MOZ_EVENT_TRACER_DONE(nsCacheService::gService, "net::cache::lock");
2719 for (uint32_t i = 0; i < doomed.Length(); ++i)
2720 doomed[i]->Release();
2721 }
2723 void
2724 nsCacheService::ReleaseObject_Locked(nsISupports * obj,
2725 nsIEventTarget * target)
2726 {
2727 gService->mLock.AssertCurrentThreadOwns();
2729 bool isCur;
2730 if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
2731 gService->mDoomedObjects.AppendElement(obj);
2732 } else {
2733 NS_ProxyRelease(target, obj);
2734 }
2735 }
2738 nsresult
2739 nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
2740 {
2741 entry->SetData(element);
2742 entry->TouchData();
2743 return NS_OK;
2744 }
2747 nsresult
2748 nsCacheService::ValidateEntry(nsCacheEntry * entry)
2749 {
2750 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2751 if (!device) return NS_ERROR_UNEXPECTED;
2753 entry->MarkValid();
2754 nsresult rv = gService->ProcessPendingRequests(entry);
2755 NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
2756 // XXX what else should be done?
2758 return rv;
2759 }
2762 int32_t
2763 nsCacheService::CacheCompressionLevel()
2764 {
2765 int32_t level = gService->mObserver->CacheCompressionLevel();
2766 return level;
2767 }
2770 void
2771 nsCacheService::DeactivateEntry(nsCacheEntry * entry)
2772 {
2773 CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
2774 nsresult rv = NS_OK;
2775 NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
2776 nsCacheDevice * device = nullptr;
2778 if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
2779 if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
2781 if (entry->IsDoomed()) {
2782 // remove from Doomed list
2783 PR_REMOVE_AND_INIT_LINK(entry);
2784 } else if (entry->IsActive()) {
2785 // remove from active entries
2786 mActiveEntries.RemoveEntry(entry);
2787 CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
2788 entry));
2789 entry->MarkInactive();
2791 // bind entry if necessary to store meta-data
2792 device = EnsureEntryHasDevice(entry);
2793 if (!device) {
2794 CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
2795 "entry %p\n",
2796 entry));
2797 NS_WARNING("DeactivateEntry: unable to bind active entry\n");
2798 return;
2799 }
2800 } else {
2801 // if mInitialized == false,
2802 // then we're shutting down and this state is okay.
2803 NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
2804 }
2806 device = entry->CacheDevice();
2807 if (device) {
2808 rv = device->DeactivateEntry(entry);
2809 if (NS_FAILED(rv)) {
2810 // increment deactivate failure count
2811 ++mDeactivateFailures;
2812 }
2813 } else {
2814 // increment deactivating unbound entry statistic
2815 ++mDeactivatedUnboundEntries;
2816 delete entry; // because no one else will
2817 }
2818 }
2821 nsresult
2822 nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
2823 {
2824 mozilla::eventtracer::AutoEventTracer tracer(
2825 entry,
2826 eventtracer::eExec,
2827 eventtracer::eDone,
2828 "net::cache::ProcessPendingRequests");
2830 nsresult rv = NS_OK;
2831 nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2832 nsCacheRequest * nextRequest;
2833 bool newWriter = false;
2835 CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
2836 (entry->IsInitialized()?"" : "Un"),
2837 (entry->IsDoomed()?"DOOMED" : ""),
2838 (entry->IsValid()? "V":"Inv"), entry));
2840 if (request == &entry->mRequestQ) return NS_OK; // no queued requests
2842 if (!entry->IsDoomed() && entry->IsInvalid()) {
2843 // 1st descriptor closed w/o MarkValid()
2844 NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
2846 #if DEBUG
2847 // verify no ACCESS_WRITE requests(shouldn't have any of these)
2848 while (request != &entry->mRequestQ) {
2849 NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
2850 "ACCESS_WRITE request should have been given a new entry");
2851 request = (nsCacheRequest *)PR_NEXT_LINK(request);
2852 }
2853 request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2854 #endif
2855 // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
2856 while (request != &entry->mRequestQ) {
2857 if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
2858 newWriter = true;
2859 CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
2860 break;
2861 }
2863 request = (nsCacheRequest *)PR_NEXT_LINK(request);
2864 }
2866 if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
2867 request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2869 // XXX what should we do if there are only READ requests in queue?
2870 // XXX serialize their accesses, give them only read access, but force them to check validate flag?
2871 // XXX or do readers simply presume the entry is valid
2872 // See fix for bug #467392 below
2873 }
2875 nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
2877 while (request != &entry->mRequestQ) {
2878 nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
2879 CACHE_LOG_DEBUG((" %sync request %p for %p\n",
2880 (request->mListener?"As":"S"), request, entry));
2882 if (request->mListener) {
2884 // Async request
2885 PR_REMOVE_AND_INIT_LINK(request);
2887 if (entry->IsDoomed()) {
2888 rv = ProcessRequest(request, false, nullptr);
2889 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
2890 rv = NS_OK;
2891 else
2892 delete request;
2894 if (NS_FAILED(rv)) {
2895 // XXX what to do?
2896 }
2897 } else if (entry->IsValid() || newWriter) {
2898 rv = entry->RequestAccess(request, &accessGranted);
2899 NS_ASSERTION(NS_SUCCEEDED(rv),
2900 "if entry is valid, RequestAccess must succeed.");
2901 // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
2903 // entry->CreateDescriptor dequeues request, and queues descriptor
2904 nsICacheEntryDescriptor *descriptor = nullptr;
2905 rv = entry->CreateDescriptor(request,
2906 accessGranted,
2907 &descriptor);
2909 // post call to listener to report error or descriptor
2910 rv = NotifyListener(request, descriptor, accessGranted, rv);
2911 delete request;
2912 if (NS_FAILED(rv)) {
2913 // XXX what to do?
2914 }
2916 } else {
2917 // read-only request to an invalid entry - need to wait for
2918 // the entry to become valid so we post an event to process
2919 // the request again later (bug #467392)
2920 nsCOMPtr<nsIRunnable> ev =
2921 new nsProcessRequestEvent(request);
2922 rv = DispatchToCacheIOThread(ev);
2923 if (NS_FAILED(rv)) {
2924 delete request; // avoid leak
2925 }
2926 }
2927 } else {
2929 // Synchronous request
2930 request->WakeUp();
2931 }
2932 if (newWriter) break; // process remaining requests after validation
2933 request = nextRequest;
2934 }
2936 return NS_OK;
2937 }
2939 bool
2940 nsCacheService::IsDoomListEmpty()
2941 {
2942 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2943 return &mDoomedEntries == entry;
2944 }
2946 void
2947 nsCacheService::ClearDoomList()
2948 {
2949 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2951 while (entry != &mDoomedEntries) {
2952 nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2954 entry->DetachDescriptors();
2955 DeactivateEntry(entry);
2956 entry = next;
2957 }
2958 }
2960 PLDHashOperator
2961 nsCacheService::GetActiveEntries(PLDHashTable * table,
2962 PLDHashEntryHdr * hdr,
2963 uint32_t number,
2964 void * arg)
2965 {
2966 static_cast<nsVoidArray *>(arg)->AppendElement(
2967 ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry);
2968 return PL_DHASH_NEXT;
2969 }
2971 struct ActiveEntryArgs
2972 {
2973 nsTArray<nsCacheEntry*>* mActiveArray;
2974 nsCacheService::DoomCheckFn mCheckFn;
2975 };
2977 void
2978 nsCacheService::DoomActiveEntries(DoomCheckFn check)
2979 {
2980 nsAutoTArray<nsCacheEntry*, 8> array;
2981 ActiveEntryArgs args = { &array, check };
2983 mActiveEntries.VisitEntries(RemoveActiveEntry, &args);
2985 uint32_t count = array.Length();
2986 for (uint32_t i=0; i < count; ++i)
2987 DoomEntry_Internal(array[i], true);
2988 }
2990 PLDHashOperator
2991 nsCacheService::RemoveActiveEntry(PLDHashTable * table,
2992 PLDHashEntryHdr * hdr,
2993 uint32_t number,
2994 void * arg)
2995 {
2996 nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
2997 NS_ASSERTION(entry, "### active entry = nullptr!");
2999 ActiveEntryArgs* args = static_cast<ActiveEntryArgs*>(arg);
3000 if (args->mCheckFn && !args->mCheckFn(entry))
3001 return PL_DHASH_NEXT;
3003 NS_ASSERTION(args->mActiveArray, "### array = nullptr!");
3004 args->mActiveArray->AppendElement(entry);
3006 // entry is being removed from the active entry list
3007 entry->MarkInactive();
3008 return PL_DHASH_REMOVE; // and continue enumerating
3009 }
3012 void
3013 nsCacheService::CloseAllStreams()
3014 {
3015 nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
3016 nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
3018 {
3019 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
3021 nsVoidArray entries;
3023 #if DEBUG
3024 // make sure there is no active entry
3025 mActiveEntries.VisitEntries(GetActiveEntries, &entries);
3026 NS_ASSERTION(entries.Count() == 0, "Bad state");
3027 #endif
3029 // Get doomed entries
3030 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
3031 while (entry != &mDoomedEntries) {
3032 nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
3033 entries.AppendElement(entry);
3034 entry = next;
3035 }
3037 // Iterate through all entries and collect input and output streams
3038 for (int32_t i = 0 ; i < entries.Count() ; i++) {
3039 entry = static_cast<nsCacheEntry *>(entries.ElementAt(i));
3041 nsTArray<nsRefPtr<nsCacheEntryDescriptor> > descs;
3042 entry->GetDescriptors(descs);
3044 for (uint32_t j = 0 ; j < descs.Length() ; j++) {
3045 if (descs[j]->mOutputWrapper)
3046 outputs.AppendElement(descs[j]->mOutputWrapper);
3048 for (int32_t k = 0 ; k < descs[j]->mInputWrappers.Count() ; k++)
3049 inputs.AppendElement(static_cast<
3050 nsCacheEntryDescriptor::nsInputStreamWrapper *>(
3051 descs[j]->mInputWrappers[k]));
3052 }
3053 }
3054 }
3056 uint32_t i;
3057 for (i = 0 ; i < inputs.Length() ; i++)
3058 inputs[i]->Close();
3060 for (i = 0 ; i < outputs.Length() ; i++)
3061 outputs[i]->Close();
3062 }
3065 bool
3066 nsCacheService::GetClearingEntries()
3067 {
3068 AssertOwnsLock();
3069 return gService->mClearingEntries;
3070 }
3072 // static
3073 void nsCacheService::GetCacheBaseDirectoty(nsIFile ** result)
3074 {
3075 *result = nullptr;
3076 if (!gService || !gService->mObserver)
3077 return;
3079 nsCOMPtr<nsIFile> directory =
3080 gService->mObserver->DiskCacheParentDirectory();
3081 if (!directory)
3082 return;
3084 directory->Clone(result);
3085 }
3087 // static
3088 void nsCacheService::GetDiskCacheDirectory(nsIFile ** result)
3089 {
3090 nsCOMPtr<nsIFile> directory;
3091 GetCacheBaseDirectoty(getter_AddRefs(directory));
3092 if (!directory)
3093 return;
3095 nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
3096 if (NS_FAILED(rv))
3097 return;
3099 directory.forget(result);
3100 }
3102 // static
3103 void nsCacheService::GetAppCacheDirectory(nsIFile ** result)
3104 {
3105 nsCOMPtr<nsIFile> directory;
3106 GetCacheBaseDirectoty(getter_AddRefs(directory));
3107 if (!directory)
3108 return;
3110 nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
3111 if (NS_FAILED(rv))
3112 return;
3114 directory.forget(result);
3115 }
3118 #if defined(PR_LOGGING)
3119 void
3120 nsCacheService::LogCacheStatistics()
3121 {
3122 uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
3123 ((double)(mCacheHits + mCacheMisses))) * 100);
3124 CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
3125 CACHE_LOG_ALWAYS((" TotalEntries = %d\n", mTotalEntries));
3126 CACHE_LOG_ALWAYS((" Cache Hits = %d\n", mCacheHits));
3127 CACHE_LOG_ALWAYS((" Cache Misses = %d\n", mCacheMisses));
3128 CACHE_LOG_ALWAYS((" Cache Hit %% = %d%%\n", hitPercentage));
3129 CACHE_LOG_ALWAYS((" Max Key Length = %d\n", mMaxKeyLength));
3130 CACHE_LOG_ALWAYS((" Max Meta Size = %d\n", mMaxMetaSize));
3131 CACHE_LOG_ALWAYS((" Max Data Size = %d\n", mMaxDataSize));
3132 CACHE_LOG_ALWAYS(("\n"));
3133 CACHE_LOG_ALWAYS((" Deactivate Failures = %d\n",
3134 mDeactivateFailures));
3135 CACHE_LOG_ALWAYS((" Deactivated Unbound Entries = %d\n",
3136 mDeactivatedUnboundEntries));
3137 }
3138 #endif
3140 nsresult
3141 nsCacheService::SetDiskSmartSize()
3142 {
3143 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
3145 if (!gService) return NS_ERROR_NOT_AVAILABLE;
3147 return gService->SetDiskSmartSize_Locked();
3148 }
3150 nsresult
3151 nsCacheService::SetDiskSmartSize_Locked()
3152 {
3153 nsresult rv;
3155 if (mozilla::net::CacheObserver::UseNewCache()) {
3156 return NS_ERROR_NOT_AVAILABLE;
3157 }
3159 if (!mObserver->DiskCacheParentDirectory())
3160 return NS_ERROR_NOT_AVAILABLE;
3162 if (!mDiskDevice)
3163 return NS_ERROR_NOT_AVAILABLE;
3165 if (!mObserver->SmartSizeEnabled())
3166 return NS_ERROR_NOT_AVAILABLE;
3168 nsAutoString cachePath;
3169 rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
3170 if (NS_SUCCEEDED(rv)) {
3171 nsCOMPtr<nsIRunnable> event =
3172 new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
3173 mObserver->ShouldUseOldMaxSmartSize());
3174 DispatchToCacheIOThread(event);
3175 } else {
3176 return NS_ERROR_FAILURE;
3177 }
3179 return NS_OK;
3180 }
3182 void
3183 nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
3184 nsIFile *aNewCacheDir,
3185 const char *aCacheSubdir)
3186 {
3187 bool same;
3188 if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
3189 return;
3191 nsCOMPtr<nsIFile> aOldCacheSubdir;
3192 aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
3194 nsresult rv = aOldCacheSubdir->AppendNative(
3195 nsDependentCString(aCacheSubdir));
3196 if (NS_FAILED(rv))
3197 return;
3199 bool exists;
3200 if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
3201 return;
3203 nsCOMPtr<nsIFile> aNewCacheSubdir;
3204 aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
3206 rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
3207 if (NS_FAILED(rv))
3208 return;
3210 nsAutoCString newPath;
3211 rv = aNewCacheSubdir->GetNativePath(newPath);
3212 if (NS_FAILED(rv))
3213 return;
3215 if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
3216 // New cache directory does not exist, try to move the old one here
3217 // rename needs an empty target directory
3219 // Make sure the parent of the target sub-dir exists
3220 rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
3221 if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
3222 nsAutoCString oldPath;
3223 rv = aOldCacheSubdir->GetNativePath(oldPath);
3224 if (NS_FAILED(rv))
3225 return;
3226 if (rename(oldPath.get(), newPath.get()) == 0)
3227 return;
3228 }
3229 }
3231 // Delay delete by 1 minute to avoid IO thrash on startup.
3232 nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
3233 }
3235 static bool
3236 IsEntryPrivate(nsCacheEntry* entry)
3237 {
3238 return entry->IsPrivate();
3239 }
3241 void
3242 nsCacheService::LeavePrivateBrowsing()
3243 {
3244 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_LEAVEPRIVATEBROWSING));
3246 gService->DoomActiveEntries(IsEntryPrivate);
3248 if (gService->mMemoryDevice) {
3249 // clear memory cache
3250 gService->mMemoryDevice->EvictPrivateEntries();
3251 }
3252 }
3254 MOZ_DEFINE_MALLOC_SIZE_OF(DiskCacheDeviceMallocSizeOf)
3256 NS_IMETHODIMP
3257 nsCacheService::CollectReports(nsIHandleReportCallback* aHandleReport,
3258 nsISupports* aData)
3259 {
3260 size_t disk = 0;
3261 if (mDiskDevice) {
3262 nsCacheServiceAutoLock
3263 lock(LOCK_TELEM(NSCACHESERVICE_DISKDEVICEHEAPSIZE));
3264 disk = mDiskDevice->SizeOfIncludingThis(DiskCacheDeviceMallocSizeOf);
3265 }
3267 size_t memory = mMemoryDevice ? mMemoryDevice->TotalSize() : 0;
3269 #define REPORT(_path, _amount, _desc) \
3270 do { \
3271 nsresult rv; \
3272 rv = aHandleReport->Callback(EmptyCString(), \
3273 NS_LITERAL_CSTRING(_path), \
3274 KIND_HEAP, UNITS_BYTES, _amount, \
3275 NS_LITERAL_CSTRING(_desc), aData); \
3276 NS_ENSURE_SUCCESS(rv, rv); \
3277 } while (0)
3279 REPORT("explicit/network/disk-cache", disk,
3280 "Memory used by the network disk cache.");
3282 REPORT("explicit/network/memory-cache", memory,
3283 "Memory used by the network memory cache.");
3285 return NS_OK;
3286 }