Bug 853360 - Implement the coneGain part of the AudioPannerNode. r=roc
[gecko.git] / netwerk / cache / nsCacheService.cpp
blob657085ee1616b495b49836a0f211e598f67ba183
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/Attributes.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/Util.h"
11 #include "necko-config.h"
13 #include "nsCache.h"
14 #include "nsCacheService.h"
15 #include "nsCacheRequest.h"
16 #include "nsCacheEntry.h"
17 #include "nsCacheEntryDescriptor.h"
18 #include "nsCacheDevice.h"
19 #include "nsMemoryCacheDevice.h"
20 #include "nsICacheVisitor.h"
21 #include "nsDiskCacheDevice.h"
22 #include "nsDiskCacheDeviceSQL.h"
24 #include "nsIObserverService.h"
25 #include "nsIPrefService.h"
26 #include "nsIPrefBranch.h"
27 #include "nsIFile.h"
28 #include "nsIOService.h"
29 #include "nsDirectoryServiceDefs.h"
30 #include "nsAppDirectoryServiceDefs.h"
31 #include "nsThreadUtils.h"
32 #include "nsProxyRelease.h"
33 #include "nsVoidArray.h"
34 #include "nsDeleteDir.h"
35 #include "nsNetCID.h"
36 #include <math.h> // for log()
37 #include "mozilla/Services.h"
38 #include "nsITimer.h"
41 #include "mozilla/net/NeckoCommon.h"
42 #include <algorithm>
44 using namespace mozilla;
46 /******************************************************************************
47 * nsCacheProfilePrefObserver
48 *****************************************************************************/
49 #define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
50 #define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
51 #define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
52 "browser.cache.disk.smart_size.first_run"
53 #define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
54 "browser.cache.disk.smart_size.enabled"
55 #define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
56 #define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
57 #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
58 #define DISK_CACHE_CAPACITY 256000
60 #define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
61 "browser.cache.disk.smart_size.use_old_max"
63 #define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
64 #define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
65 #define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
66 #define OFFLINE_CACHE_CAPACITY 512000
68 #define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
69 #define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
70 #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
72 #define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
73 #define CACHE_COMPRESSION_LEVEL 1
75 #define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
76 #define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
78 static const char * observerList[] = {
79 "profile-before-change",
80 "profile-do-change",
81 NS_XPCOM_SHUTDOWN_OBSERVER_ID,
82 "last-pb-context-exited",
83 "suspend_process_notification",
84 "resume_process_notification"
87 static const char * prefList[] = {
88 DISK_CACHE_ENABLE_PREF,
89 DISK_CACHE_SMART_SIZE_ENABLED_PREF,
90 DISK_CACHE_CAPACITY_PREF,
91 DISK_CACHE_DIR_PREF,
92 DISK_CACHE_MAX_ENTRY_SIZE_PREF,
93 DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
94 OFFLINE_CACHE_ENABLE_PREF,
95 OFFLINE_CACHE_CAPACITY_PREF,
96 OFFLINE_CACHE_DIR_PREF,
97 MEMORY_CACHE_ENABLE_PREF,
98 MEMORY_CACHE_CAPACITY_PREF,
99 MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
100 CACHE_COMPRESSION_LEVEL_PREF,
101 SANITIZE_ON_SHUTDOWN_PREF,
102 CLEAR_ON_SHUTDOWN_PREF
105 // Cache sizes, in KB
106 const int32_t DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
107 const int32_t MIN_CACHE_SIZE = 50 * 1024; // 50 MB
108 #ifdef ANDROID
109 const int32_t MAX_CACHE_SIZE = 200 * 1024; // 200 MB
110 const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024; // 200 MB
111 #else
112 const int32_t MAX_CACHE_SIZE = 350 * 1024; // 350 MB
113 const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
114 #endif
115 // Default cache size was 50 MB for many years until FF 4:
116 const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
118 class nsCacheProfilePrefObserver : public nsIObserver
120 public:
121 NS_DECL_ISUPPORTS
122 NS_DECL_NSIOBSERVER
124 nsCacheProfilePrefObserver()
125 : mHaveProfile(false)
126 , mDiskCacheEnabled(false)
127 , mDiskCacheCapacity(0)
128 , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
129 , mSmartSizeEnabled(false)
130 , mShouldUseOldMaxSmartSize(false)
131 , mOfflineCacheEnabled(false)
132 , mOfflineCacheCapacity(0)
133 , mMemoryCacheEnabled(true)
134 , mMemoryCacheCapacity(-1)
135 , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
136 , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
137 , mSanitizeOnShutdown(false)
138 , mClearCacheOnShutdown(false)
142 virtual ~nsCacheProfilePrefObserver() {}
144 nsresult Install();
145 void Remove();
146 nsresult ReadPrefs(nsIPrefBranch* branch);
148 bool DiskCacheEnabled();
149 int32_t DiskCacheCapacity() { return mDiskCacheCapacity; }
150 void SetDiskCacheCapacity(int32_t);
151 int32_t DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
152 nsIFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
153 bool SmartSizeEnabled() { return mSmartSizeEnabled; }
155 bool ShouldUseOldMaxSmartSize() { return mShouldUseOldMaxSmartSize; }
156 void SetUseNewMaxSmartSize(bool useNew) { mShouldUseOldMaxSmartSize = !useNew; }
158 bool OfflineCacheEnabled();
159 int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
160 nsIFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
162 bool MemoryCacheEnabled();
163 int32_t MemoryCacheCapacity();
164 int32_t MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
166 int32_t CacheCompressionLevel();
168 bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
170 static uint32_t GetSmartCacheSize(const nsAString& cachePath,
171 uint32_t currentSize,
172 bool shouldUseOldMaxSmartSize);
174 bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
176 private:
177 bool mHaveProfile;
179 bool mDiskCacheEnabled;
180 int32_t mDiskCacheCapacity; // in kilobytes
181 int32_t mDiskCacheMaxEntrySize; // in kilobytes
182 nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
183 bool mSmartSizeEnabled;
185 bool mShouldUseOldMaxSmartSize;
187 bool mOfflineCacheEnabled;
188 int32_t mOfflineCacheCapacity; // in kilobytes
189 nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
191 bool mMemoryCacheEnabled;
192 int32_t mMemoryCacheCapacity; // in kilobytes
193 int32_t mMemoryCacheMaxEntrySize; // in kilobytes
195 int32_t mCacheCompressionLevel;
197 bool mSanitizeOnShutdown;
198 bool mClearCacheOnShutdown;
201 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver)
203 class nsSetDiskSmartSizeCallback MOZ_FINAL : public nsITimerCallback
205 public:
206 NS_DECL_ISUPPORTS
208 NS_IMETHOD Notify(nsITimer* aTimer) {
209 if (nsCacheService::gService) {
210 nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
211 nsCacheService::gService->SetDiskSmartSize_Locked();
212 nsCacheService::gService->mSmartSizeTimer = nullptr;
214 return NS_OK;
218 NS_IMPL_THREADSAFE_ISUPPORTS1(nsSetDiskSmartSizeCallback, nsITimerCallback)
220 // Runnable sent to main thread after the cache IO thread calculates available
221 // disk space, so that there is no race in setting mDiskCacheCapacity.
222 class nsSetSmartSizeEvent: public nsRunnable
224 public:
225 nsSetSmartSizeEvent(int32_t smartSize)
226 : mSmartSize(smartSize) {}
228 NS_IMETHOD Run()
230 NS_ASSERTION(NS_IsMainThread(),
231 "Setting smart size data off the main thread");
233 // Main thread may have already called nsCacheService::Shutdown
234 if (!nsCacheService::IsInitialized())
235 return NS_ERROR_NOT_AVAILABLE;
237 // Ensure smart sizing wasn't switched off while event was pending.
238 // It is safe to access the observer without the lock since we are
239 // on the main thread and the value changes only on the main thread.
240 if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
241 return NS_OK;
243 nsCacheService::SetDiskCacheCapacity(mSmartSize);
245 nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
246 if (!ps ||
247 NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
248 NS_WARNING("Failed to set smart size pref");
250 return NS_OK;
253 private:
254 int32_t mSmartSize;
258 // Runnable sent from main thread to cacheIO thread
259 class nsGetSmartSizeEvent: public nsRunnable
261 public:
262 nsGetSmartSizeEvent(const nsAString& cachePath, uint32_t currentSize,
263 bool shouldUseOldMaxSmartSize)
264 : mCachePath(cachePath)
265 , mCurrentSize(currentSize)
266 , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
269 // Calculates user's disk space available on a background thread and
270 // dispatches this value back to the main thread.
271 NS_IMETHOD Run()
273 uint32_t size;
274 size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
275 mCurrentSize,
276 mShouldUseOldMaxSmartSize);
277 NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
278 return NS_OK;
281 private:
282 nsString mCachePath;
283 uint32_t mCurrentSize;
284 bool mShouldUseOldMaxSmartSize;
287 class nsBlockOnCacheThreadEvent : public nsRunnable {
288 public:
289 nsBlockOnCacheThreadEvent()
292 NS_IMETHOD Run()
294 nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
295 #ifdef PR_LOGGING
296 CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
297 #endif
298 nsCacheService::gService->mCondVar.Notify();
299 return NS_OK;
304 nsresult
305 nsCacheProfilePrefObserver::Install()
307 // install profile-change observer
308 nsCOMPtr<nsIObserverService> observerService =
309 mozilla::services::GetObserverService();
310 if (!observerService)
311 return NS_ERROR_FAILURE;
313 nsresult rv, rv2 = NS_OK;
314 for (unsigned int i=0; i<ArrayLength(observerList); i++) {
315 rv = observerService->AddObserver(this, observerList[i], false);
316 if (NS_FAILED(rv))
317 rv2 = rv;
320 // install preferences observer
321 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
322 if (!branch) return NS_ERROR_FAILURE;
324 for (unsigned int i=0; i<ArrayLength(prefList); i++) {
325 rv = branch->AddObserver(prefList[i], this, false);
326 if (NS_FAILED(rv))
327 rv2 = rv;
330 // Determine if we have a profile already
331 // Install() is called *after* the profile-after-change notification
332 // when there is only a single profile, or it is specified on the
333 // commandline at startup.
334 // In that case, we detect the presence of a profile by the existence
335 // of the NS_APP_USER_PROFILE_50_DIR directory.
337 nsCOMPtr<nsIFile> directory;
338 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
339 getter_AddRefs(directory));
340 if (NS_SUCCEEDED(rv))
341 mHaveProfile = true;
343 rv = ReadPrefs(branch);
344 NS_ENSURE_SUCCESS(rv, rv);
346 return rv2;
350 void
351 nsCacheProfilePrefObserver::Remove()
353 // remove Observer Service observers
354 nsCOMPtr<nsIObserverService> obs =
355 mozilla::services::GetObserverService();
356 if (obs) {
357 for (unsigned int i=0; i<ArrayLength(observerList); i++) {
358 obs->RemoveObserver(this, observerList[i]);
362 // remove Pref Service observers
363 nsCOMPtr<nsIPrefBranch> prefs =
364 do_GetService(NS_PREFSERVICE_CONTRACTID);
365 if (!prefs)
366 return;
367 for (unsigned int i=0; i<ArrayLength(prefList); i++)
368 prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
371 void
372 nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
374 mDiskCacheCapacity = std::max(0, capacity);
378 NS_IMETHODIMP
379 nsCacheProfilePrefObserver::Observe(nsISupports * subject,
380 const char * topic,
381 const PRUnichar * data_unicode)
383 nsresult rv;
384 NS_ConvertUTF16toUTF8 data(data_unicode);
385 CACHE_LOG_ALWAYS(("Observe [topic=%s data=%s]\n", topic, data.get()));
387 if (!nsCacheService::IsInitialized()) {
388 if (!strcmp("resume_process_notification", topic)) {
389 // A suspended process has a closed cache, so re-open it here.
390 nsCacheService::GlobalInstance()->Init();
392 return NS_OK;
395 if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
396 // xpcom going away, shutdown cache service
397 nsCacheService::GlobalInstance()->Shutdown();
398 } else if (!strcmp("profile-before-change", topic)) {
399 // profile before change
400 mHaveProfile = false;
402 // XXX shutdown devices
403 nsCacheService::OnProfileShutdown(!strcmp("shutdown-cleanse",
404 data.get()));
406 } else if (!strcmp("suspend_process_notification", topic)) {
407 // A suspended process may never return, so shutdown the cache to reduce
408 // cache corruption.
409 nsCacheService::GlobalInstance()->Shutdown();
410 } else if (!strcmp("profile-do-change", topic)) {
411 // profile after change
412 mHaveProfile = true;
413 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
414 ReadPrefs(branch);
415 nsCacheService::OnProfileChanged();
417 } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
419 // ignore pref changes until we're done switch profiles
420 if (!mHaveProfile)
421 return NS_OK;
423 nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
424 if (NS_FAILED(rv))
425 return rv;
427 // which preference changed?
428 if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
430 rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
431 &mDiskCacheEnabled);
432 if (NS_FAILED(rv))
433 return rv;
434 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
436 } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
438 int32_t capacity = 0;
439 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
440 if (NS_FAILED(rv))
441 return rv;
442 mDiskCacheCapacity = std::max(0, capacity);
443 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
445 // Update the cache capacity when smart sizing is turned on/off
446 } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
447 // Is the update because smartsizing was turned on, or off?
448 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
449 &mSmartSizeEnabled);
450 if (NS_FAILED(rv))
451 return rv;
452 int32_t newCapacity = 0;
453 if (mSmartSizeEnabled) {
454 nsCacheService::SetDiskSmartSize();
455 } else {
456 // Smart sizing switched off: use user specified size
457 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
458 if (NS_FAILED(rv))
459 return rv;
460 mDiskCacheCapacity = std::max(0, newCapacity);
461 nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
463 } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
464 rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
465 &mShouldUseOldMaxSmartSize);
466 if (NS_FAILED(rv))
467 return rv;
468 } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
469 int32_t newMaxSize;
470 rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
471 &newMaxSize);
472 if (NS_FAILED(rv))
473 return rv;
475 mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
476 nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
478 #if 0
479 } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
480 // XXX We probaby don't want to respond to this pref except after
481 // XXX profile changes. Ideally, there should be somekind of user
482 // XXX notification that the pref change won't take effect until
483 // XXX the next time the profile changes (browser launch)
484 #endif
485 } else
487 // which preference changed?
488 if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
490 rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
491 &mOfflineCacheEnabled);
492 if (NS_FAILED(rv)) return rv;
493 nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
495 } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
497 int32_t capacity = 0;
498 rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
499 if (NS_FAILED(rv)) return rv;
500 mOfflineCacheCapacity = std::max(0, capacity);
501 nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
502 #if 0
503 } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
504 // XXX We probaby don't want to respond to this pref except after
505 // XXX profile changes. Ideally, there should be some kind of user
506 // XXX notification that the pref change won't take effect until
507 // XXX the next time the profile changes (browser launch)
508 #endif
509 } else
511 if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
513 rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
514 &mMemoryCacheEnabled);
515 if (NS_FAILED(rv))
516 return rv;
517 nsCacheService::SetMemoryCache();
519 } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
521 mMemoryCacheCapacity = -1;
522 (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
523 &mMemoryCacheCapacity);
524 nsCacheService::SetMemoryCache();
525 } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
526 int32_t newMaxSize;
527 rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
528 &newMaxSize);
529 if (NS_FAILED(rv))
530 return rv;
532 mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
533 nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
534 } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
535 mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
536 (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
537 &mCacheCompressionLevel);
538 mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
539 mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
540 } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
541 rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
542 &mSanitizeOnShutdown);
543 if (NS_FAILED(rv))
544 return rv;
545 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
546 } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
547 rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
548 &mClearCacheOnShutdown);
549 if (NS_FAILED(rv))
550 return rv;
551 nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
553 } else if (!strcmp("last-pb-context-exited", topic)) {
554 nsCacheService::LeavePrivateBrowsing();
557 return NS_OK;
560 // Returns default ("smart") size (in KB) of cache, given available disk space
561 // (also in KB)
562 static uint32_t
563 SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
565 uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
567 if (availKB > 100 * 1024 * 1024)
568 return maxSize; // skip computing if we're over 100 GB
570 // Grow/shrink in 10 MB units, deliberately, so that in the common case we
571 // don't shrink cache and evict items every time we startup (it's important
572 // that we don't slow down startup benchmarks).
573 uint32_t sz10MBs = 0;
574 uint32_t avail10MBs = availKB / (1024*10);
576 // .5% of space above 25 GB
577 if (avail10MBs > 2500) {
578 sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
579 avail10MBs = 2500;
581 // 1% of space between 7GB -> 25 GB
582 if (avail10MBs > 700) {
583 sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
584 avail10MBs = 700;
586 // 5% of space between 500 MB -> 7 GB
587 if (avail10MBs > 50) {
588 sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
589 avail10MBs = 50;
592 #ifdef ANDROID
593 // On Android, smaller/older devices may have very little storage and
594 // device owners may be sensitive to storage footprint: Use a smaller
595 // percentage of available space and a smaller minimum.
597 // 20% of space up to 500 MB (10 MB min)
598 sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
599 #else
600 // 40% of space up to 500 MB (50 MB min)
601 sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
602 #endif
604 return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
607 /* Computes our best guess for the default size of the user's disk cache,
608 * based on the amount of space they have free on their hard drive.
609 * We use a tiered scheme: the more space available,
610 * the larger the disk cache will be. However, we do not want
611 * to enable the disk cache to grow to an unbounded size, so the larger the
612 * user's available space is, the smaller of a percentage we take. We set a
613 * lower bound of 50MB and an upper bound of 1GB.
615 *@param: None.
616 *@return: The size that the user's disk cache should default to, in kBytes.
618 uint32_t
619 nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
620 uint32_t currentSize,
621 bool shouldUseOldMaxSmartSize)
623 // Check for free space on device where cache directory lives
624 nsresult rv;
625 nsCOMPtr<nsIFile>
626 cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
627 if (NS_FAILED(rv) || !cacheDirectory)
628 return DEFAULT_CACHE_SIZE;
629 rv = cacheDirectory->InitWithPath(cachePath);
630 if (NS_FAILED(rv))
631 return DEFAULT_CACHE_SIZE;
632 int64_t bytesAvailable;
633 rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
634 if (NS_FAILED(rv))
635 return DEFAULT_CACHE_SIZE;
637 return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
638 currentSize),
639 shouldUseOldMaxSmartSize);
642 /* Determine if we are permitted to dynamically size the user's disk cache based
643 * on their disk space available. We may do this so long as the pref
644 * smart_size.enabled is true.
646 bool
647 nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
648 firstRun)
650 nsresult rv;
651 if (firstRun) {
652 // check if user has set cache size in the past
653 bool userSet;
654 rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
655 if (NS_FAILED(rv)) userSet = true;
656 if (userSet) {
657 int32_t oldCapacity;
658 // If user explicitly set cache size to be smaller than old default
659 // of 50 MB, then keep user's value. Otherwise use smart sizing.
660 rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
661 if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
662 mSmartSizeEnabled = false;
663 branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
664 mSmartSizeEnabled);
665 return mSmartSizeEnabled;
668 // Set manual setting to MAX cache size as starting val for any
669 // adjustment by user: (bug 559942 comment 65)
670 int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
671 branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
674 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
675 &mSmartSizeEnabled);
676 if (NS_FAILED(rv))
677 mSmartSizeEnabled = false;
678 return mSmartSizeEnabled;
682 nsresult
683 nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
685 nsresult rv = NS_OK;
687 // read disk cache device prefs
688 mDiskCacheEnabled = true; // presume disk cache is enabled
689 (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
691 mDiskCacheCapacity = DISK_CACHE_CAPACITY;
692 (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
693 mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
695 (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
696 &mDiskCacheMaxEntrySize);
697 mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
699 (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
700 NS_GET_IID(nsIFile),
701 getter_AddRefs(mDiskCacheParentDirectory));
703 (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
704 &mShouldUseOldMaxSmartSize);
706 if (!mDiskCacheParentDirectory) {
707 nsCOMPtr<nsIFile> directory;
709 // try to get the disk cache parent directory
710 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
711 getter_AddRefs(directory));
712 if (NS_FAILED(rv)) {
713 // try to get the profile directory (there may not be a profile yet)
714 nsCOMPtr<nsIFile> profDir;
715 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
716 getter_AddRefs(profDir));
717 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
718 getter_AddRefs(directory));
719 if (!directory)
720 directory = profDir;
721 else if (profDir) {
722 nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
723 "Cache");
726 // use file cache in build tree only if asked, to avoid cache dir litter
727 if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
728 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
729 getter_AddRefs(directory));
731 if (directory)
732 mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
734 if (mDiskCacheParentDirectory) {
735 bool firstSmartSizeRun;
736 rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
737 &firstSmartSizeRun);
738 if (NS_FAILED(rv))
739 firstSmartSizeRun = false;
740 if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
741 // Avoid evictions: use previous cache size until smart size event
742 // updates mDiskCacheCapacity
743 rv = branch->GetIntPref(firstSmartSizeRun ?
744 DISK_CACHE_CAPACITY_PREF :
745 DISK_CACHE_SMART_SIZE_PREF,
746 &mDiskCacheCapacity);
747 if (NS_FAILED(rv))
748 mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
751 if (firstSmartSizeRun) {
752 // It is no longer our first run
753 rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
754 false);
755 if (NS_FAILED(rv))
756 NS_WARNING("Failed setting first_run pref in ReadPrefs.");
760 // read offline cache device prefs
761 mOfflineCacheEnabled = true; // presume offline cache is enabled
762 (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
763 &mOfflineCacheEnabled);
765 mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
766 (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
767 &mOfflineCacheCapacity);
768 mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
770 (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
771 NS_GET_IID(nsIFile),
772 getter_AddRefs(mOfflineCacheParentDirectory));
774 if (!mOfflineCacheParentDirectory) {
775 nsCOMPtr<nsIFile> directory;
777 // try to get the offline cache parent directory
778 rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
779 getter_AddRefs(directory));
780 if (NS_FAILED(rv)) {
781 // try to get the profile directory (there may not be a profile yet)
782 nsCOMPtr<nsIFile> profDir;
783 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
784 getter_AddRefs(profDir));
785 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
786 getter_AddRefs(directory));
787 if (!directory)
788 directory = profDir;
789 else if (profDir) {
790 nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
791 "OfflineCache");
794 #if DEBUG
795 if (!directory) {
796 // use current process directory during development
797 rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
798 getter_AddRefs(directory));
800 #endif
801 if (directory)
802 mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
805 // read memory cache device prefs
806 (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
808 mMemoryCacheCapacity = -1;
809 (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
810 &mMemoryCacheCapacity);
812 (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
813 &mMemoryCacheMaxEntrySize);
814 mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
816 // read cache compression level pref
817 mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
818 (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
819 &mCacheCompressionLevel);
820 mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
821 mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
823 // read cache shutdown sanitization prefs
824 (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
825 &mSanitizeOnShutdown);
826 (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
827 &mClearCacheOnShutdown);
829 return rv;
832 nsresult
833 nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
835 if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
836 return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
839 nsresult
840 nsCacheService::SyncWithCacheIOThread()
842 gService->mLock.AssertCurrentThreadOwns();
843 if (!gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
845 nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
847 // dispatch event - it will notify the monitor when it's done
848 nsresult rv =
849 gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
850 if (NS_FAILED(rv)) {
851 NS_WARNING("Failed dispatching block-event");
852 return NS_ERROR_UNEXPECTED;
855 // wait until notified, then return
856 rv = gService->mCondVar.Wait();
858 return rv;
862 bool
863 nsCacheProfilePrefObserver::DiskCacheEnabled()
865 if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
866 return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
870 bool
871 nsCacheProfilePrefObserver::OfflineCacheEnabled()
873 if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
874 return false;
876 return mOfflineCacheEnabled;
880 bool
881 nsCacheProfilePrefObserver::MemoryCacheEnabled()
883 if (mMemoryCacheCapacity == 0) return false;
884 return mMemoryCacheEnabled;
889 * MemoryCacheCapacity
891 * If the browser.cache.memory.capacity preference is positive, we use that
892 * value for the amount of memory available for the cache.
894 * If browser.cache.memory.capacity is zero, the memory cache is disabled.
896 * If browser.cache.memory.capacity is negative or not present, we use a
897 * formula that grows less than linearly with the amount of system memory,
898 * with an upper limit on the cache size. No matter how much physical RAM is
899 * present, the default cache size would not exceed 32 MB. This maximum would
900 * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
902 * RAM Cache
903 * --- -----
904 * 32 Mb 2 Mb
905 * 64 Mb 4 Mb
906 * 128 Mb 6 Mb
907 * 256 Mb 10 Mb
908 * 512 Mb 14 Mb
909 * 1024 Mb 18 Mb
910 * 2048 Mb 24 Mb
911 * 4096 Mb 30 Mb
913 * The equation for this is (for cache size C and memory size K (kbytes)):
914 * x = log2(K) - 14
915 * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
916 * if (C > 32) C = 32
919 int32_t
920 nsCacheProfilePrefObserver::MemoryCacheCapacity()
922 int32_t capacity = mMemoryCacheCapacity;
923 if (capacity >= 0) {
924 CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
925 return capacity;
928 static uint64_t bytes = PR_GetPhysicalMemorySize();
929 CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
931 // If getting the physical memory failed, arbitrarily assume
932 // 32 MB of RAM. We use a low default to have a reasonable
933 // size on all the devices we support.
934 if (bytes == 0)
935 bytes = 32 * 1024 * 1024;
937 // Conversion from unsigned int64_t to double doesn't work on all platforms.
938 // We need to truncate the value at INT64_MAX to make sure we don't
939 // overflow.
940 if (bytes > INT64_MAX)
941 bytes = INT64_MAX;
943 uint64_t kbytes = bytes >> 10;
945 double kBytesD = double(kbytes);
947 double x = log(kBytesD)/log(2.0) - 14;
948 if (x > 0) {
949 capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
950 if (capacity > 32)
951 capacity = 32;
952 capacity *= 1024;
953 } else {
954 capacity = 0;
957 return capacity;
960 int32_t
961 nsCacheProfilePrefObserver::CacheCompressionLevel()
963 return mCacheCompressionLevel;
966 /******************************************************************************
967 * nsProcessRequestEvent
968 *****************************************************************************/
970 class nsProcessRequestEvent : public nsRunnable {
971 public:
972 nsProcessRequestEvent(nsCacheRequest *aRequest)
974 mRequest = aRequest;
977 NS_IMETHOD Run()
979 nsresult rv;
981 NS_ASSERTION(mRequest->mListener,
982 "Sync OpenCacheEntry() posted to background thread!");
984 nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
985 rv = nsCacheService::gService->ProcessRequest(mRequest,
986 false,
987 nullptr);
989 // Don't delete the request if it was queued
990 if (!(mRequest->IsBlocking() &&
991 rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
992 delete mRequest;
994 return NS_OK;
997 protected:
998 virtual ~nsProcessRequestEvent() {}
1000 private:
1001 nsCacheRequest *mRequest;
1004 /******************************************************************************
1005 * nsDoomEvent
1006 *****************************************************************************/
1008 class nsDoomEvent : public nsRunnable {
1009 public:
1010 nsDoomEvent(nsCacheSession *session,
1011 const nsACString &key,
1012 nsICacheListener *listener)
1014 mKey = *session->ClientID();
1015 mKey.Append(':');
1016 mKey.Append(key);
1017 mStoragePolicy = session->StoragePolicy();
1018 mListener = listener;
1019 mThread = do_GetCurrentThread();
1020 // We addref the listener here and release it in nsNotifyDoomListener
1021 // on the callers thread. If posting of nsNotifyDoomListener event fails
1022 // we leak the listener which is better than releasing it on a wrong
1023 // thread.
1024 NS_IF_ADDREF(mListener);
1027 NS_IMETHOD Run()
1029 nsCacheServiceAutoLock lock(LOCK_TELEM(NSDOOMEVENT_RUN));
1031 bool foundActive = true;
1032 nsresult status = NS_ERROR_NOT_AVAILABLE;
1033 nsCacheEntry *entry;
1034 entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
1035 if (!entry) {
1036 bool collision = false;
1037 foundActive = false;
1038 entry = nsCacheService::gService->SearchCacheDevices(&mKey,
1039 mStoragePolicy,
1040 &collision);
1043 if (entry) {
1044 status = NS_OK;
1045 nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
1048 if (mListener) {
1049 mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
1050 NS_DISPATCH_NORMAL);
1051 // posted event will release the reference on the correct thread
1052 mListener = nullptr;
1055 return NS_OK;
1058 private:
1059 nsCString mKey;
1060 nsCacheStoragePolicy mStoragePolicy;
1061 nsICacheListener *mListener;
1062 nsCOMPtr<nsIThread> mThread;
1065 /******************************************************************************
1066 * nsCacheService
1067 *****************************************************************************/
1068 nsCacheService * nsCacheService::gService = nullptr;
1070 NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService)
1072 nsCacheService::nsCacheService()
1073 : mObserver(nullptr),
1074 mLock("nsCacheService.mLock"),
1075 mCondVar(mLock, "nsCacheService.mCondVar"),
1076 mInitialized(false),
1077 mClearingEntries(false),
1078 mEnableMemoryDevice(true),
1079 mEnableDiskDevice(true),
1080 mMemoryDevice(nullptr),
1081 mDiskDevice(nullptr),
1082 mOfflineDevice(nullptr),
1083 mTotalEntries(0),
1084 mCacheHits(0),
1085 mCacheMisses(0),
1086 mMaxKeyLength(0),
1087 mMaxDataSize(0),
1088 mMaxMetaSize(0),
1089 mDeactivateFailures(0),
1090 mDeactivatedUnboundEntries(0)
1092 NS_ASSERTION(gService==nullptr, "multiple nsCacheService instances!");
1093 gService = this;
1095 // create list of cache devices
1096 PR_INIT_CLIST(&mDoomedEntries);
1097 mCustomOfflineDevices.Init();
1100 nsCacheService::~nsCacheService()
1102 if (mInitialized) // Shutdown hasn't been called yet.
1103 (void) Shutdown();
1105 if (mObserver) {
1106 mObserver->Remove();
1107 NS_RELEASE(mObserver);
1110 gService = nullptr;
1114 nsresult
1115 nsCacheService::Init()
1117 // Thie method must be called on the main thread because mCacheIOThread must
1118 // only be modified on the main thread.
1119 if (!NS_IsMainThread()) {
1120 NS_ERROR("nsCacheService::Init called off the main thread");
1121 return NS_ERROR_NOT_SAME_THREAD;
1124 NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
1125 if (mInitialized)
1126 return NS_ERROR_ALREADY_INITIALIZED;
1128 if (mozilla::net::IsNeckoChild()) {
1129 return NS_ERROR_UNEXPECTED;
1132 CACHE_LOG_INIT();
1134 nsresult rv = NS_NewNamedThread("Cache I/O",
1135 getter_AddRefs(mCacheIOThread));
1136 if (NS_FAILED(rv)) {
1137 NS_RUNTIMEABORT("Can't create cache IO thread");
1140 rv = nsDeleteDir::Init();
1141 if (NS_FAILED(rv)) {
1142 NS_WARNING("Can't initialize nsDeleteDir");
1145 // initialize hashtable for active cache entries
1146 rv = mActiveEntries.Init();
1147 if (NS_FAILED(rv)) return rv;
1149 // create profile/preference observer
1150 if (!mObserver) {
1151 mObserver = new nsCacheProfilePrefObserver();
1152 NS_ADDREF(mObserver);
1153 mObserver->Install();
1156 mEnableDiskDevice = mObserver->DiskCacheEnabled();
1157 mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
1158 mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
1160 mInitialized = true;
1161 return NS_OK;
1164 // static
1165 PLDHashOperator
1166 nsCacheService::ShutdownCustomCacheDeviceEnum(const nsAString& aProfileDir,
1167 nsRefPtr<nsOfflineCacheDevice>& aDevice,
1168 void* aUserArg)
1170 aDevice->Shutdown();
1171 return PL_DHASH_REMOVE;
1174 void
1175 nsCacheService::Shutdown()
1177 // This method must be called on the main thread because mCacheIOThread must
1178 // only be modified on the main thread.
1179 if (!NS_IsMainThread()) {
1180 NS_RUNTIMEABORT("nsCacheService::Shutdown called off the main thread");
1183 nsCOMPtr<nsIThread> cacheIOThread;
1184 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
1186 bool shouldSanitize = false;
1187 nsCOMPtr<nsIFile> parentDir;
1190 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
1191 NS_ASSERTION(mInitialized,
1192 "can't shutdown nsCacheService unless it has been initialized.");
1193 if (!mInitialized)
1194 return;
1196 mClearingEntries = true;
1197 DoomActiveEntries(nullptr);
1200 CloseAllStreams();
1203 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
1204 NS_ASSERTION(mInitialized, "Bad state");
1206 mInitialized = false;
1208 // Clear entries
1209 ClearDoomList();
1211 if (mSmartSizeTimer) {
1212 mSmartSizeTimer->Cancel();
1213 mSmartSizeTimer = nullptr;
1216 // Make sure to wait for any pending cache-operations before
1217 // proceeding with destructive actions (bug #620660)
1218 (void) SyncWithCacheIOThread();
1220 // obtain the disk cache directory in case we need to sanitize it
1221 parentDir = mObserver->DiskCacheParentDirectory();
1222 shouldSanitize = mObserver->SanitizeAtShutdown();
1224 // deallocate memory and disk caches
1225 delete mMemoryDevice;
1226 mMemoryDevice = nullptr;
1228 delete mDiskDevice;
1229 mDiskDevice = nullptr;
1231 if (mOfflineDevice)
1232 mOfflineDevice->Shutdown();
1234 NS_IF_RELEASE(mOfflineDevice);
1236 mCustomOfflineDevices.Enumerate(&nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
1238 #ifdef PR_LOGGING
1239 LogCacheStatistics();
1240 #endif
1242 mClearingEntries = false;
1243 mCacheIOThread.swap(cacheIOThread);
1246 if (cacheIOThread)
1247 cacheIOThread->Shutdown();
1249 if (shouldSanitize) {
1250 nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
1251 if (NS_SUCCEEDED(rv)) {
1252 bool exists;
1253 if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
1254 nsDeleteDir::DeleteDir(parentDir, false);
1256 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
1257 nsDeleteDir::Shutdown(shouldSanitize);
1258 } else {
1259 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
1260 nsDeleteDir::Shutdown(shouldSanitize);
1265 nsresult
1266 nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
1268 nsresult rv;
1270 if (aOuter != nullptr)
1271 return NS_ERROR_NO_AGGREGATION;
1273 nsCacheService * cacheService = new nsCacheService();
1274 if (cacheService == nullptr)
1275 return NS_ERROR_OUT_OF_MEMORY;
1277 NS_ADDREF(cacheService);
1278 rv = cacheService->Init();
1279 if (NS_SUCCEEDED(rv)) {
1280 rv = cacheService->QueryInterface(aIID, aResult);
1282 NS_RELEASE(cacheService);
1283 return rv;
1287 NS_IMETHODIMP
1288 nsCacheService::CreateSession(const char * clientID,
1289 nsCacheStoragePolicy storagePolicy,
1290 bool streamBased,
1291 nsICacheSession **result)
1293 *result = nullptr;
1295 if (this == nullptr) return NS_ERROR_NOT_AVAILABLE;
1297 nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
1298 if (!session) return NS_ERROR_OUT_OF_MEMORY;
1300 NS_ADDREF(*result = session);
1302 return NS_OK;
1306 nsresult
1307 nsCacheService::EvictEntriesForSession(nsCacheSession * session)
1309 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1310 return gService->EvictEntriesForClient(session->ClientID()->get(),
1311 session->StoragePolicy());
1314 namespace {
1316 class EvictionNotifierRunnable : public nsRunnable
1318 public:
1319 EvictionNotifierRunnable(nsISupports* aSubject)
1320 : mSubject(aSubject)
1323 NS_DECL_NSIRUNNABLE
1325 private:
1326 nsCOMPtr<nsISupports> mSubject;
1329 NS_IMETHODIMP
1330 EvictionNotifierRunnable::Run()
1332 nsCOMPtr<nsIObserverService> obsSvc =
1333 mozilla::services::GetObserverService();
1334 if (obsSvc) {
1335 obsSvc->NotifyObservers(mSubject,
1336 NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
1337 nullptr);
1339 return NS_OK;
1342 } // anonymous namespace
1344 nsresult
1345 nsCacheService::EvictEntriesForClient(const char * clientID,
1346 nsCacheStoragePolicy storagePolicy)
1348 nsRefPtr<EvictionNotifierRunnable> r = new EvictionNotifierRunnable(this);
1349 NS_DispatchToMainThread(r);
1351 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
1352 nsresult res = NS_OK;
1354 if (storagePolicy == nsICache::STORE_ANYWHERE ||
1355 storagePolicy == nsICache::STORE_ON_DISK) {
1357 if (mEnableDiskDevice) {
1358 nsresult rv = NS_OK;
1359 if (!mDiskDevice)
1360 rv = CreateDiskDevice();
1361 if (mDiskDevice)
1362 rv = mDiskDevice->EvictEntries(clientID);
1363 if (NS_FAILED(rv))
1364 res = rv;
1368 // Only clear the offline cache if it has been specifically asked for.
1369 if (storagePolicy == nsICache::STORE_OFFLINE) {
1370 if (mEnableOfflineDevice) {
1371 nsresult rv = NS_OK;
1372 if (!mOfflineDevice)
1373 rv = CreateOfflineDevice();
1374 if (mOfflineDevice)
1375 rv = mOfflineDevice->EvictEntries(clientID);
1376 if (NS_FAILED(rv))
1377 res = rv;
1381 if (storagePolicy == nsICache::STORE_ANYWHERE ||
1382 storagePolicy == nsICache::STORE_IN_MEMORY) {
1383 // If there is no memory device, there is no need to evict it...
1384 if (mMemoryDevice) {
1385 nsresult rv = mMemoryDevice->EvictEntries(clientID);
1386 if (NS_FAILED(rv))
1387 res = rv;
1391 return res;
1395 nsresult
1396 nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
1397 bool * result)
1399 if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
1400 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
1402 *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
1403 return NS_OK;
1407 nsresult
1408 nsCacheService::DoomEntry(nsCacheSession *session,
1409 const nsACString &key,
1410 nsICacheListener *listener)
1412 CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
1413 session, PromiseFlatCString(key).get()));
1414 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1416 if (!gService->mInitialized)
1417 return NS_ERROR_NOT_INITIALIZED;
1419 return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
1423 bool
1424 nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
1426 if (gService->mEnableMemoryDevice &&
1427 (storagePolicy == nsICache::STORE_ANYWHERE ||
1428 storagePolicy == nsICache::STORE_IN_MEMORY)) {
1429 return true;
1431 if (gService->mEnableDiskDevice &&
1432 (storagePolicy == nsICache::STORE_ANYWHERE ||
1433 storagePolicy == nsICache::STORE_ON_DISK)) {
1434 return true;
1436 if (gService->mEnableOfflineDevice &&
1437 storagePolicy == nsICache::STORE_OFFLINE) {
1438 return true;
1441 return false;
1444 NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
1446 NS_ENSURE_ARG_POINTER(visitor);
1448 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
1450 if (!(mEnableDiskDevice || mEnableMemoryDevice))
1451 return NS_ERROR_NOT_AVAILABLE;
1453 // XXX record the fact that a visitation is in progress,
1454 // XXX i.e. keep list of visitors in progress.
1456 nsresult rv = NS_OK;
1457 // If there is no memory device, there are then also no entries to visit...
1458 if (mMemoryDevice) {
1459 rv = mMemoryDevice->Visit(visitor);
1460 if (NS_FAILED(rv)) return rv;
1463 if (mEnableDiskDevice) {
1464 if (!mDiskDevice) {
1465 rv = CreateDiskDevice();
1466 if (NS_FAILED(rv)) return rv;
1468 rv = mDiskDevice->Visit(visitor);
1469 if (NS_FAILED(rv)) return rv;
1472 if (mEnableOfflineDevice) {
1473 if (!mOfflineDevice) {
1474 rv = CreateOfflineDevice();
1475 if (NS_FAILED(rv)) return rv;
1477 rv = mOfflineDevice->Visit(visitor);
1478 if (NS_FAILED(rv)) return rv;
1481 // XXX notify any shutdown process that visitation is complete for THIS visitor.
1482 // XXX keep queue of visitors
1484 return NS_OK;
1488 NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
1490 return EvictEntriesForClient(nullptr, storagePolicy);
1493 NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
1495 NS_ENSURE_ARG_POINTER(aCacheIOTarget);
1497 // Because mCacheIOThread can only be changed on the main thread, it can be
1498 // read from the main thread without the lock. This is useful to prevent
1499 // blocking the main thread on other cache operations.
1500 if (!NS_IsMainThread()) {
1501 Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
1504 nsresult rv;
1505 if (mCacheIOThread) {
1506 NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
1507 rv = NS_OK;
1508 } else {
1509 *aCacheIOTarget = nullptr;
1510 rv = NS_ERROR_NOT_AVAILABLE;
1513 if (!NS_IsMainThread()) {
1514 Unlock();
1517 return rv;
1521 * Internal Methods
1523 nsresult
1524 nsCacheService::CreateDiskDevice()
1526 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1527 if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
1528 if (mDiskDevice) return NS_OK;
1530 mDiskDevice = new nsDiskCacheDevice;
1531 if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
1533 // set the preferences
1534 mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
1535 mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
1536 mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
1538 nsresult rv = mDiskDevice->Init();
1539 if (NS_FAILED(rv)) {
1540 #if DEBUG
1541 printf("###\n");
1542 printf("### mDiskDevice->Init() failed (0x%.8x)\n",
1543 static_cast<uint32_t>(rv));
1544 printf("### - disabling disk cache for this session.\n");
1545 printf("###\n");
1546 #endif
1547 mEnableDiskDevice = false;
1548 delete mDiskDevice;
1549 mDiskDevice = nullptr;
1550 return rv;
1553 Telemetry::Accumulate(Telemetry::DISK_CACHE_SMART_SIZE_USING_OLD_MAX,
1554 mObserver->ShouldUseOldMaxSmartSize());
1556 NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
1558 // Disk device is usually created during the startup. Delay smart size
1559 // calculation to avoid possible massive IO caused by eviction of entries
1560 // in case the new smart size is smaller than current cache usage.
1561 mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
1562 if (NS_SUCCEEDED(rv)) {
1563 rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
1564 1000*60*3,
1565 nsITimer::TYPE_ONE_SHOT);
1566 if (NS_FAILED(rv)) {
1567 NS_WARNING("Failed to post smart size timer");
1568 mSmartSizeTimer = nullptr;
1570 } else {
1571 NS_WARNING("Can't create smart size timer");
1573 // Ignore state of the timer and return success since the purpose of the
1574 // method (create the disk-device) has been fulfilled
1576 return NS_OK;
1579 // Runnable sent from cache thread to main thread
1580 class nsDisableOldMaxSmartSizePrefEvent: public nsRunnable
1582 public:
1583 nsDisableOldMaxSmartSizePrefEvent() {}
1585 NS_IMETHOD Run()
1587 // Main thread may have already called nsCacheService::Shutdown
1588 if (!nsCacheService::IsInitialized())
1589 return NS_ERROR_NOT_AVAILABLE;
1591 nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
1592 if (!branch) {
1593 return NS_ERROR_NOT_AVAILABLE;
1596 nsresult rv = branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
1597 if (NS_FAILED(rv)) {
1598 NS_WARNING("Failed to disable old max smart size");
1599 return rv;
1602 nsCacheService::SetDiskSmartSize();
1604 if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch, false)) {
1605 rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
1606 if (NS_FAILED(rv)) {
1607 NS_WARNING("Failed to set cache capacity pref");
1611 return NS_OK;
1615 void
1616 nsCacheService::MarkStartingFresh()
1618 if (!gService->mObserver->ShouldUseOldMaxSmartSize()) {
1619 // Already using new max, nothing to do here
1620 return;
1623 gService->mObserver->SetUseNewMaxSmartSize(true);
1625 // We always dispatch an event here because we don't want to deal with lock
1626 // reentrance issues.
1627 NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
1630 nsresult
1631 nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
1633 if (!mOfflineDevice) {
1634 nsresult rv = CreateOfflineDevice();
1635 NS_ENSURE_SUCCESS(rv, rv);
1638 NS_ADDREF(*aDevice = mOfflineDevice);
1639 return NS_OK;
1642 nsresult
1643 nsCacheService::GetCustomOfflineDevice(nsIFile *aProfileDir,
1644 int32_t aQuota,
1645 nsOfflineCacheDevice **aDevice)
1647 nsresult rv;
1649 nsAutoString profilePath;
1650 rv = aProfileDir->GetPath(profilePath);
1651 NS_ENSURE_SUCCESS(rv, rv);
1653 if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
1654 rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
1655 NS_ENSURE_SUCCESS(rv, rv);
1657 (*aDevice)->SetAutoShutdown();
1658 mCustomOfflineDevices.Put(profilePath, *aDevice);
1661 return NS_OK;
1664 nsresult
1665 nsCacheService::CreateOfflineDevice()
1667 CACHE_LOG_ALWAYS(("Creating default offline device"));
1669 if (mOfflineDevice) return NS_OK;
1670 if (!nsCacheService::IsInitialized()) {
1671 return NS_ERROR_NOT_AVAILABLE;
1674 nsresult rv = CreateCustomOfflineDevice(
1675 mObserver->OfflineCacheParentDirectory(),
1676 mObserver->OfflineCacheCapacity(),
1677 &mOfflineDevice);
1678 NS_ENSURE_SUCCESS(rv, rv);
1680 return NS_OK;
1683 nsresult
1684 nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
1685 int32_t aQuota,
1686 nsOfflineCacheDevice **aDevice)
1688 NS_ENSURE_ARG(aProfileDir);
1690 #if defined(PR_LOGGING)
1691 nsAutoCString profilePath;
1692 aProfileDir->GetNativePath(profilePath);
1693 CACHE_LOG_ALWAYS(("Creating custom offline device, %s, %d",
1694 profilePath.BeginReading(), aQuota));
1695 #endif
1697 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1698 if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
1700 *aDevice = new nsOfflineCacheDevice;
1702 NS_ADDREF(*aDevice);
1704 // set the preferences
1705 (*aDevice)->SetCacheParentDirectory(aProfileDir);
1706 (*aDevice)->SetCapacity(aQuota);
1708 nsresult rv = (*aDevice)->Init();
1709 if (NS_FAILED(rv)) {
1710 CACHE_LOG_DEBUG(("OfflineDevice->Init() failed (0x%.8x)\n", rv));
1711 CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
1713 NS_RELEASE(*aDevice);
1715 return rv;
1718 nsresult
1719 nsCacheService::CreateMemoryDevice()
1721 if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
1722 if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
1723 if (mMemoryDevice) return NS_OK;
1725 mMemoryDevice = new nsMemoryCacheDevice;
1726 if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
1728 // set preference
1729 int32_t capacity = mObserver->MemoryCacheCapacity();
1730 CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
1731 mMemoryDevice->SetCapacity(capacity);
1732 mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
1734 nsresult rv = mMemoryDevice->Init();
1735 if (NS_FAILED(rv)) {
1736 NS_WARNING("Initialization of Memory Cache failed.");
1737 delete mMemoryDevice;
1738 mMemoryDevice = nullptr;
1741 return rv;
1744 nsresult
1745 nsCacheService::RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice)
1747 nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
1748 if (!profileDir)
1749 return NS_ERROR_UNEXPECTED;
1751 nsAutoString profilePath;
1752 nsresult rv = profileDir->GetPath(profilePath);
1753 NS_ENSURE_SUCCESS(rv, rv);
1755 mCustomOfflineDevices.Remove(profilePath);
1756 return NS_OK;
1759 nsresult
1760 nsCacheService::CreateRequest(nsCacheSession * session,
1761 const nsACString & clientKey,
1762 nsCacheAccessMode accessRequested,
1763 bool blockingMode,
1764 nsICacheListener * listener,
1765 nsCacheRequest ** request)
1767 NS_ASSERTION(request, "CreateRequest: request is null");
1769 nsAutoCString key(*session->ClientID());
1770 key.Append(':');
1771 key.Append(clientKey);
1773 if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
1775 // create request
1776 *request = new nsCacheRequest(key, listener, accessRequested,
1777 blockingMode, session);
1779 if (!listener) return NS_OK; // we're sync, we're done.
1781 // get the request's thread
1782 (*request)->mThread = do_GetCurrentThread();
1784 return NS_OK;
1788 class nsCacheListenerEvent : public nsRunnable
1790 public:
1791 nsCacheListenerEvent(nsICacheListener *listener,
1792 nsICacheEntryDescriptor *descriptor,
1793 nsCacheAccessMode accessGranted,
1794 nsresult status)
1795 : mListener(listener) // transfers reference
1796 , mDescriptor(descriptor) // transfers reference (may be null)
1797 , mAccessGranted(accessGranted)
1798 , mStatus(status)
1801 NS_IMETHOD Run()
1803 mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
1805 NS_RELEASE(mListener);
1806 NS_IF_RELEASE(mDescriptor);
1807 return NS_OK;
1810 private:
1811 // We explicitly leak mListener or mDescriptor if Run is not called
1812 // because otherwise we cannot guarantee that they are destroyed on
1813 // the right thread.
1815 nsICacheListener *mListener;
1816 nsICacheEntryDescriptor *mDescriptor;
1817 nsCacheAccessMode mAccessGranted;
1818 nsresult mStatus;
1822 nsresult
1823 nsCacheService::NotifyListener(nsCacheRequest * request,
1824 nsICacheEntryDescriptor * descriptor,
1825 nsCacheAccessMode accessGranted,
1826 nsresult status)
1828 NS_ASSERTION(request->mThread, "no thread set in async request!");
1830 // Swap ownership, and release listener on target thread...
1831 nsICacheListener *listener = request->mListener;
1832 request->mListener = nullptr;
1834 nsCOMPtr<nsIRunnable> ev =
1835 new nsCacheListenerEvent(listener, descriptor,
1836 accessGranted, status);
1837 if (!ev) {
1838 // Better to leak listener and descriptor if we fail because we don't
1839 // want to destroy them inside the cache service lock or on potentially
1840 // the wrong thread.
1841 return NS_ERROR_OUT_OF_MEMORY;
1844 return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1848 nsresult
1849 nsCacheService::ProcessRequest(nsCacheRequest * request,
1850 bool calledFromOpenCacheEntry,
1851 nsICacheEntryDescriptor ** result)
1853 // !!! must be called with mLock held !!!
1854 nsresult rv;
1855 nsCacheEntry * entry = nullptr;
1856 nsCacheEntry * doomedEntry = nullptr;
1857 nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
1858 if (result) *result = nullptr;
1860 while(1) { // Activate entry loop
1861 rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
1862 if (NS_FAILED(rv)) break;
1864 while(1) { // Request Access loop
1865 NS_ASSERTION(entry, "no entry in Request Access loop!");
1866 // entry->RequestAccess queues request on entry
1867 rv = entry->RequestAccess(request, &accessGranted);
1868 if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
1870 if (request->IsBlocking()) {
1871 if (request->mListener) {
1872 // async exits - validate, doom, or close will resume
1873 return rv;
1876 // XXX this is probably wrong...
1877 Unlock();
1878 rv = request->WaitForValidation();
1879 Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
1882 PR_REMOVE_AND_INIT_LINK(request);
1883 if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
1884 // okay, we're ready to process this request, request access again
1886 if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
1888 if (entry->IsNotInUse()) {
1889 // this request was the last one keeping it around, so get rid of it
1890 DeactivateEntry(entry);
1892 // loop back around to look for another entry
1895 if (NS_SUCCEEDED(rv) && request->mProfileDir) {
1896 // Custom cache directory has been demanded. Preset the cache device.
1897 if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
1898 // Failsafe check: this is implemented only for offline cache atm.
1899 rv = NS_ERROR_FAILURE;
1900 } else {
1901 nsRefPtr<nsOfflineCacheDevice> customCacheDevice;
1902 rv = GetCustomOfflineDevice(request->mProfileDir, -1,
1903 getter_AddRefs(customCacheDevice));
1904 if (NS_SUCCEEDED(rv))
1905 entry->SetCustomCacheDevice(customCacheDevice);
1909 nsICacheEntryDescriptor *descriptor = nullptr;
1911 if (NS_SUCCEEDED(rv))
1912 rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
1914 // If doomedEntry is set, ActivatEntry() doomed an existing entry and
1915 // created a new one for that cache-key. However, any pending requests
1916 // on the doomed entry were not processed and we need to do that here.
1917 // This must be done after adding the created entry to list of active
1918 // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
1919 // (see bug ##561313). It is also important to do this after creating a
1920 // descriptor for this request, or some other request may end up being
1921 // executed first for the newly created entry.
1922 // Finally, it is worth to emphasize that if doomedEntry is set,
1923 // ActivateEntry() created a new entry for the request, which will be
1924 // initialized by RequestAccess() and they both should have returned NS_OK.
1925 if (doomedEntry) {
1926 (void) ProcessPendingRequests(doomedEntry);
1927 if (doomedEntry->IsNotInUse())
1928 DeactivateEntry(doomedEntry);
1929 doomedEntry = nullptr;
1932 if (request->mListener) { // Asynchronous
1934 if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
1935 return rv; // skip notifying listener, just return rv to caller
1937 // call listener to report error or descriptor
1938 nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
1939 if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
1940 rv = rv2; // trigger delete request
1942 } else { // Synchronous
1943 *result = descriptor;
1945 return rv;
1949 nsresult
1950 nsCacheService::OpenCacheEntry(nsCacheSession * session,
1951 const nsACString & key,
1952 nsCacheAccessMode accessRequested,
1953 bool blockingMode,
1954 nsICacheListener * listener,
1955 nsICacheEntryDescriptor ** result)
1957 CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
1958 session, PromiseFlatCString(key).get(), accessRequested,
1959 blockingMode));
1960 NS_ASSERTION(gService, "nsCacheService::gService is null.");
1961 if (result)
1962 *result = nullptr;
1964 if (!gService->mInitialized)
1965 return NS_ERROR_NOT_INITIALIZED;
1967 nsCacheRequest * request = nullptr;
1969 nsresult rv = gService->CreateRequest(session,
1970 key,
1971 accessRequested,
1972 blockingMode,
1973 listener,
1974 &request);
1975 if (NS_FAILED(rv)) return rv;
1977 CACHE_LOG_DEBUG(("Created request %p\n", request));
1979 // Process the request on the background thread if we are on the main thread
1980 // and the the request is asynchronous
1981 if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
1982 nsCOMPtr<nsIRunnable> ev =
1983 new nsProcessRequestEvent(request);
1984 rv = DispatchToCacheIOThread(ev);
1986 // delete request if we didn't post the event
1987 if (NS_FAILED(rv))
1988 delete request;
1990 else {
1992 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
1993 rv = gService->ProcessRequest(request, true, result);
1995 // delete requests that have completed
1996 if (!(listener && blockingMode &&
1997 (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
1998 delete request;
2001 return rv;
2005 nsresult
2006 nsCacheService::ActivateEntry(nsCacheRequest * request,
2007 nsCacheEntry ** result,
2008 nsCacheEntry ** doomedEntry)
2010 CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
2011 if (!mInitialized || mClearingEntries)
2012 return NS_ERROR_NOT_AVAILABLE;
2014 nsresult rv = NS_OK;
2016 NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
2017 if (result) *result = nullptr;
2018 if (doomedEntry) *doomedEntry = nullptr;
2019 if ((!request) || (!result) || (!doomedEntry))
2020 return NS_ERROR_NULL_POINTER;
2022 // check if the request can be satisfied
2023 if (!mEnableMemoryDevice && !request->IsStreamBased())
2024 return NS_ERROR_FAILURE;
2025 if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
2026 return NS_ERROR_FAILURE;
2028 // search active entries (including those not bound to device)
2029 nsCacheEntry *entry = mActiveEntries.GetEntry(&(request->mKey));
2030 CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
2032 if (!entry) {
2033 // search cache devices for entry
2034 bool collision = false;
2035 entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(), &collision);
2036 CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
2037 request, entry));
2038 // When there is a hashkey collision just refuse to cache it...
2039 if (collision) return NS_ERROR_CACHE_IN_USE;
2041 if (entry) entry->MarkInitialized();
2042 } else {
2043 NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
2046 if (entry) {
2047 ++mCacheHits;
2048 entry->Fetched();
2049 } else {
2050 ++mCacheMisses;
2053 if (entry &&
2054 ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
2055 ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
2056 (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
2057 request->WillDoomEntriesIfExpired()))))
2060 // this is FORCE-WRITE request or the entry has expired
2061 // we doom entry without processing pending requests, but store it in
2062 // doomedEntry which causes pending requests to be processed below
2063 rv = DoomEntry_Internal(entry, false);
2064 *doomedEntry = entry;
2065 if (NS_FAILED(rv)) {
2066 // XXX what to do? Increment FailedDooms counter?
2068 entry = nullptr;
2071 if (!entry) {
2072 if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
2073 // this is a READ-ONLY request
2074 rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
2075 goto error;
2078 entry = new nsCacheEntry(request->mKey,
2079 request->IsStreamBased(),
2080 request->StoragePolicy());
2081 if (!entry)
2082 return NS_ERROR_OUT_OF_MEMORY;
2084 if (request->IsPrivate())
2085 entry->MarkPrivate();
2087 entry->Fetched();
2088 ++mTotalEntries;
2090 // XXX we could perform an early bind in some cases based on storage policy
2093 if (!entry->IsActive()) {
2094 rv = mActiveEntries.AddEntry(entry);
2095 if (NS_FAILED(rv)) goto error;
2096 CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
2097 entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
2099 *result = entry;
2100 return NS_OK;
2102 error:
2103 *result = nullptr;
2104 delete entry;
2105 return rv;
2109 nsCacheEntry *
2110 nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
2112 Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
2113 nsCacheEntry * entry = nullptr;
2115 CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
2117 *collision = false;
2118 if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
2119 // If there is no memory device, then there is nothing to search...
2120 if (mMemoryDevice) {
2121 entry = mMemoryDevice->FindEntry(key, collision);
2122 CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
2123 "collision: %d\n", key->get(), entry, collision));
2127 if (!entry &&
2128 ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
2130 if (mEnableDiskDevice) {
2131 if (!mDiskDevice) {
2132 nsresult rv = CreateDiskDevice();
2133 if (NS_FAILED(rv))
2134 return nullptr;
2137 entry = mDiskDevice->FindEntry(key, collision);
2141 if (!entry && (policy == nsICache::STORE_OFFLINE ||
2142 (policy == nsICache::STORE_ANYWHERE &&
2143 gIOService->IsOffline()))) {
2145 if (mEnableOfflineDevice) {
2146 if (!mOfflineDevice) {
2147 nsresult rv = CreateOfflineDevice();
2148 if (NS_FAILED(rv))
2149 return nullptr;
2152 entry = mOfflineDevice->FindEntry(key, collision);
2156 return entry;
2160 nsCacheDevice *
2161 nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
2163 nsCacheDevice * device = entry->CacheDevice();
2164 // return device if found, possibly null if the entry is doomed i.e prevent
2165 // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
2166 if (device || entry->IsDoomed()) return device;
2168 int64_t predictedDataSize = entry->PredictedDataSize();
2169 if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
2170 // this is the default
2171 if (!mDiskDevice) {
2172 (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
2175 if (mDiskDevice) {
2176 // Bypass the cache if Content-Length says the entry will be too big
2177 if (predictedDataSize != -1 &&
2178 mDiskDevice->EntryIsTooBig(predictedDataSize)) {
2179 DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
2180 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
2181 return nullptr;
2184 entry->MarkBinding(); // enter state of binding
2185 nsresult rv = mDiskDevice->BindEntry(entry);
2186 entry->ClearBinding(); // exit state of binding
2187 if (NS_SUCCEEDED(rv))
2188 device = mDiskDevice;
2192 // if we can't use mDiskDevice, try mMemoryDevice
2193 if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
2194 if (!mMemoryDevice) {
2195 (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
2197 if (mMemoryDevice) {
2198 // Bypass the cache if Content-Length says entry will be too big
2199 if (predictedDataSize != -1 &&
2200 mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
2201 DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
2202 NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
2203 return nullptr;
2206 entry->MarkBinding(); // enter state of binding
2207 nsresult rv = mMemoryDevice->BindEntry(entry);
2208 entry->ClearBinding(); // exit state of binding
2209 if (NS_SUCCEEDED(rv))
2210 device = mMemoryDevice;
2214 if (!device && entry->IsStreamData() &&
2215 entry->IsAllowedOffline() && mEnableOfflineDevice) {
2216 if (!mOfflineDevice) {
2217 (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
2220 device = entry->CustomCacheDevice()
2221 ? entry->CustomCacheDevice()
2222 : mOfflineDevice;
2224 if (device) {
2225 entry->MarkBinding();
2226 nsresult rv = device->BindEntry(entry);
2227 entry->ClearBinding();
2228 if (NS_FAILED(rv))
2229 device = nullptr;
2233 if (device)
2234 entry->SetCacheDevice(device);
2235 return device;
2238 nsresult
2239 nsCacheService::DoomEntry(nsCacheEntry * entry)
2241 return gService->DoomEntry_Internal(entry, true);
2245 nsresult
2246 nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
2247 bool doProcessPendingRequests)
2249 if (entry->IsDoomed()) return NS_OK;
2251 CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
2252 nsresult rv = NS_OK;
2253 entry->MarkDoomed();
2255 NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
2256 nsCacheDevice * device = entry->CacheDevice();
2257 if (device) device->DoomEntry(entry);
2259 if (entry->IsActive()) {
2260 // remove from active entries
2261 mActiveEntries.RemoveEntry(entry);
2262 CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
2263 entry->MarkInactive();
2266 // put on doom list to wait for descriptors to close
2267 NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
2268 PR_APPEND_LINK(entry, &mDoomedEntries);
2270 // handle pending requests only if we're supposed to
2271 if (doProcessPendingRequests) {
2272 // tell pending requests to get on with their lives...
2273 rv = ProcessPendingRequests(entry);
2275 // All requests have been removed, but there may still be open descriptors
2276 if (entry->IsNotInUse()) {
2277 DeactivateEntry(entry); // tell device to get rid of it
2280 return rv;
2284 void
2285 nsCacheService::OnProfileShutdown(bool cleanse)
2287 if (!gService) return;
2288 if (!gService->mInitialized) {
2289 // The cache service has been shut down, but someone is still holding
2290 // a reference to it. Ignore this call.
2291 return;
2294 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
2295 gService->mClearingEntries = true;
2296 gService->DoomActiveEntries(nullptr);
2299 gService->CloseAllStreams();
2301 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
2302 gService->ClearDoomList();
2304 // Make sure to wait for any pending cache-operations before
2305 // proceeding with destructive actions (bug #620660)
2306 (void) SyncWithCacheIOThread();
2308 if (gService->mDiskDevice && gService->mEnableDiskDevice) {
2309 if (cleanse)
2310 gService->mDiskDevice->EvictEntries(nullptr);
2312 gService->mDiskDevice->Shutdown();
2314 gService->mEnableDiskDevice = false;
2316 if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
2317 if (cleanse)
2318 gService->mOfflineDevice->EvictEntries(nullptr);
2320 gService->mOfflineDevice->Shutdown();
2322 gService->mCustomOfflineDevices.Enumerate(
2323 &nsCacheService::ShutdownCustomCacheDeviceEnum, nullptr);
2325 gService->mEnableOfflineDevice = false;
2327 if (gService->mMemoryDevice) {
2328 // clear memory cache
2329 gService->mMemoryDevice->EvictEntries(nullptr);
2332 gService->mClearingEntries = false;
2336 void
2337 nsCacheService::OnProfileChanged()
2339 if (!gService) return;
2341 CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
2343 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
2345 gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2346 gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2347 gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2349 if (gService->mDiskDevice) {
2350 gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
2351 gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
2353 // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
2354 nsresult rv = gService->mDiskDevice->Init();
2355 if (NS_FAILED(rv)) {
2356 NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
2357 gService->mEnableDiskDevice = false;
2358 // XXX delete mDiskDevice?
2362 if (gService->mOfflineDevice) {
2363 gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
2364 gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
2366 // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
2367 nsresult rv = gService->mOfflineDevice->Init();
2368 if (NS_FAILED(rv)) {
2369 NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
2370 gService->mEnableOfflineDevice = false;
2371 // XXX delete mOfflineDevice?
2375 // If memoryDevice exists, reset its size to the new profile
2376 if (gService->mMemoryDevice) {
2377 if (gService->mEnableMemoryDevice) {
2378 // make sure that capacity is reset to the right value
2379 int32_t capacity = gService->mObserver->MemoryCacheCapacity();
2380 CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2381 capacity));
2382 gService->mMemoryDevice->SetCapacity(capacity);
2383 } else {
2384 // tell memory device to evict everything
2385 CACHE_LOG_DEBUG(("memory device disabled\n"));
2386 gService->mMemoryDevice->SetCapacity(0);
2387 // Don't delete memory device, because some entries may be active still...
2393 void
2394 nsCacheService::SetDiskCacheEnabled(bool enabled)
2396 if (!gService) return;
2397 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
2398 gService->mEnableDiskDevice = enabled;
2402 void
2403 nsCacheService::SetDiskCacheCapacity(int32_t capacity)
2405 if (!gService) return;
2406 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
2408 if (gService->mDiskDevice) {
2409 gService->mDiskDevice->SetCapacity(capacity);
2412 gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
2415 void
2416 nsCacheService::SetDiskCacheMaxEntrySize(int32_t maxSize)
2418 if (!gService) return;
2419 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
2421 if (gService->mDiskDevice) {
2422 gService->mDiskDevice->SetMaxEntrySize(maxSize);
2426 void
2427 nsCacheService::SetMemoryCacheMaxEntrySize(int32_t maxSize)
2429 if (!gService) return;
2430 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
2432 if (gService->mMemoryDevice) {
2433 gService->mMemoryDevice->SetMaxEntrySize(maxSize);
2437 void
2438 nsCacheService::SetOfflineCacheEnabled(bool enabled)
2440 if (!gService) return;
2441 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
2442 gService->mEnableOfflineDevice = enabled;
2445 void
2446 nsCacheService::SetOfflineCacheCapacity(int32_t capacity)
2448 if (!gService) return;
2449 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
2451 if (gService->mOfflineDevice) {
2452 gService->mOfflineDevice->SetCapacity(capacity);
2455 gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
2459 void
2460 nsCacheService::SetMemoryCache()
2462 if (!gService) return;
2464 CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
2466 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
2468 gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
2470 if (gService->mEnableMemoryDevice) {
2471 if (gService->mMemoryDevice) {
2472 int32_t capacity = gService->mObserver->MemoryCacheCapacity();
2473 // make sure that capacity is reset to the right value
2474 CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
2475 capacity));
2476 gService->mMemoryDevice->SetCapacity(capacity);
2478 } else {
2479 if (gService->mMemoryDevice) {
2480 // tell memory device to evict everything
2481 CACHE_LOG_DEBUG(("memory device disabled\n"));
2482 gService->mMemoryDevice->SetCapacity(0);
2483 // Don't delete memory device, because some entries may be active still...
2489 /******************************************************************************
2490 * static methods for nsCacheEntryDescriptor
2491 *****************************************************************************/
2492 void
2493 nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
2495 // ask entry to remove descriptor
2496 nsCacheEntry * entry = descriptor->CacheEntry();
2497 bool doomEntry;
2498 bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
2500 if (!entry->IsValid()) {
2501 gService->ProcessPendingRequests(entry);
2504 if (doomEntry) {
2505 gService->DoomEntry_Internal(entry, true);
2506 return;
2509 if (!stillActive) {
2510 gService->DeactivateEntry(entry);
2515 nsresult
2516 nsCacheService::GetFileForEntry(nsCacheEntry * entry,
2517 nsIFile ** result)
2519 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2520 if (!device) return NS_ERROR_UNEXPECTED;
2522 return device->GetFileForEntry(entry, result);
2526 nsresult
2527 nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
2528 nsCacheAccessMode mode,
2529 uint32_t offset,
2530 nsIInputStream ** result)
2532 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2533 if (!device) return NS_ERROR_UNEXPECTED;
2535 return device->OpenInputStreamForEntry(entry, mode, offset, result);
2538 nsresult
2539 nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
2540 nsCacheAccessMode mode,
2541 uint32_t offset,
2542 nsIOutputStream ** result)
2544 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2545 if (!device) return NS_ERROR_UNEXPECTED;
2547 return device->OpenOutputStreamForEntry(entry, mode, offset, result);
2551 nsresult
2552 nsCacheService::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
2554 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2555 if (!device) return NS_ERROR_UNEXPECTED;
2557 return device->OnDataSizeChange(entry, deltaSize);
2560 void
2561 nsCacheService::Lock(mozilla::Telemetry::ID mainThreadLockerID)
2563 mozilla::Telemetry::ID lockerID;
2564 mozilla::Telemetry::ID generalID;
2566 if (NS_IsMainThread()) {
2567 lockerID = mainThreadLockerID;
2568 generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
2569 } else {
2570 lockerID = mozilla::Telemetry::HistogramCount;
2571 generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
2574 TimeStamp start(TimeStamp::Now());
2576 gService->mLock.Lock();
2578 TimeStamp stop(TimeStamp::Now());
2580 // Telemetry isn't thread safe on its own, but this is OK because we're
2581 // protecting it with the cache lock.
2582 if (lockerID != mozilla::Telemetry::HistogramCount) {
2583 mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
2585 mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
2588 void
2589 nsCacheService::Unlock()
2591 gService->mLock.AssertCurrentThreadOwns();
2593 nsTArray<nsISupports*> doomed;
2594 doomed.SwapElements(gService->mDoomedObjects);
2596 gService->mLock.Unlock();
2598 for (uint32_t i = 0; i < doomed.Length(); ++i)
2599 doomed[i]->Release();
2602 void
2603 nsCacheService::ReleaseObject_Locked(nsISupports * obj,
2604 nsIEventTarget * target)
2606 gService->mLock.AssertCurrentThreadOwns();
2608 bool isCur;
2609 if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
2610 gService->mDoomedObjects.AppendElement(obj);
2611 } else {
2612 NS_ProxyRelease(target, obj);
2617 nsresult
2618 nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
2620 entry->SetData(element);
2621 entry->TouchData();
2622 return NS_OK;
2626 nsresult
2627 nsCacheService::ValidateEntry(nsCacheEntry * entry)
2629 nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
2630 if (!device) return NS_ERROR_UNEXPECTED;
2632 entry->MarkValid();
2633 nsresult rv = gService->ProcessPendingRequests(entry);
2634 NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
2635 // XXX what else should be done?
2637 return rv;
2641 int32_t
2642 nsCacheService::CacheCompressionLevel()
2644 int32_t level = gService->mObserver->CacheCompressionLevel();
2645 return level;
2649 void
2650 nsCacheService::DeactivateEntry(nsCacheEntry * entry)
2652 CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
2653 nsresult rv = NS_OK;
2654 NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
2655 nsCacheDevice * device = nullptr;
2657 if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
2658 if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
2660 if (entry->IsDoomed()) {
2661 // remove from Doomed list
2662 PR_REMOVE_AND_INIT_LINK(entry);
2663 } else if (entry->IsActive()) {
2664 // remove from active entries
2665 mActiveEntries.RemoveEntry(entry);
2666 CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
2667 entry));
2668 entry->MarkInactive();
2670 // bind entry if necessary to store meta-data
2671 device = EnsureEntryHasDevice(entry);
2672 if (!device) {
2673 CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
2674 "entry %p\n",
2675 entry));
2676 NS_WARNING("DeactivateEntry: unable to bind active entry\n");
2677 return;
2679 } else {
2680 // if mInitialized == false,
2681 // then we're shutting down and this state is okay.
2682 NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
2685 device = entry->CacheDevice();
2686 if (device) {
2687 rv = device->DeactivateEntry(entry);
2688 if (NS_FAILED(rv)) {
2689 // increment deactivate failure count
2690 ++mDeactivateFailures;
2692 } else {
2693 // increment deactivating unbound entry statistic
2694 ++mDeactivatedUnboundEntries;
2695 delete entry; // because no one else will
2700 nsresult
2701 nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
2703 nsresult rv = NS_OK;
2704 nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2705 nsCacheRequest * nextRequest;
2706 bool newWriter = false;
2708 CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
2709 (entry->IsInitialized()?"" : "Un"),
2710 (entry->IsDoomed()?"DOOMED" : ""),
2711 (entry->IsValid()? "V":"Inv"), entry));
2713 if (request == &entry->mRequestQ) return NS_OK; // no queued requests
2715 if (!entry->IsDoomed() && entry->IsInvalid()) {
2716 // 1st descriptor closed w/o MarkValid()
2717 NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
2719 #if DEBUG
2720 // verify no ACCESS_WRITE requests(shouldn't have any of these)
2721 while (request != &entry->mRequestQ) {
2722 NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
2723 "ACCESS_WRITE request should have been given a new entry");
2724 request = (nsCacheRequest *)PR_NEXT_LINK(request);
2726 request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2727 #endif
2728 // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
2729 while (request != &entry->mRequestQ) {
2730 if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
2731 newWriter = true;
2732 CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
2733 break;
2736 request = (nsCacheRequest *)PR_NEXT_LINK(request);
2739 if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
2740 request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
2742 // XXX what should we do if there are only READ requests in queue?
2743 // XXX serialize their accesses, give them only read access, but force them to check validate flag?
2744 // XXX or do readers simply presume the entry is valid
2745 // See fix for bug #467392 below
2748 nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
2750 while (request != &entry->mRequestQ) {
2751 nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
2752 CACHE_LOG_DEBUG((" %sync request %p for %p\n",
2753 (request->mListener?"As":"S"), request, entry));
2755 if (request->mListener) {
2757 // Async request
2758 PR_REMOVE_AND_INIT_LINK(request);
2760 if (entry->IsDoomed()) {
2761 rv = ProcessRequest(request, false, nullptr);
2762 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
2763 rv = NS_OK;
2764 else
2765 delete request;
2767 if (NS_FAILED(rv)) {
2768 // XXX what to do?
2770 } else if (entry->IsValid() || newWriter) {
2771 rv = entry->RequestAccess(request, &accessGranted);
2772 NS_ASSERTION(NS_SUCCEEDED(rv),
2773 "if entry is valid, RequestAccess must succeed.");
2774 // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
2776 // entry->CreateDescriptor dequeues request, and queues descriptor
2777 nsICacheEntryDescriptor *descriptor = nullptr;
2778 rv = entry->CreateDescriptor(request,
2779 accessGranted,
2780 &descriptor);
2782 // post call to listener to report error or descriptor
2783 rv = NotifyListener(request, descriptor, accessGranted, rv);
2784 delete request;
2785 if (NS_FAILED(rv)) {
2786 // XXX what to do?
2789 } else {
2790 // read-only request to an invalid entry - need to wait for
2791 // the entry to become valid so we post an event to process
2792 // the request again later (bug #467392)
2793 nsCOMPtr<nsIRunnable> ev =
2794 new nsProcessRequestEvent(request);
2795 rv = DispatchToCacheIOThread(ev);
2796 if (NS_FAILED(rv)) {
2797 delete request; // avoid leak
2800 } else {
2802 // Synchronous request
2803 request->WakeUp();
2805 if (newWriter) break; // process remaining requests after validation
2806 request = nextRequest;
2809 return NS_OK;
2812 bool
2813 nsCacheService::IsDoomListEmpty()
2815 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2816 return &mDoomedEntries == entry;
2819 void
2820 nsCacheService::ClearDoomList()
2822 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2824 while (entry != &mDoomedEntries) {
2825 nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2827 entry->DetachDescriptors();
2828 DeactivateEntry(entry);
2829 entry = next;
2833 PLDHashOperator
2834 nsCacheService::GetActiveEntries(PLDHashTable * table,
2835 PLDHashEntryHdr * hdr,
2836 uint32_t number,
2837 void * arg)
2839 static_cast<nsVoidArray *>(arg)->AppendElement(
2840 ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry);
2841 return PL_DHASH_NEXT;
2844 struct ActiveEntryArgs
2846 nsTArray<nsCacheEntry*>* mActiveArray;
2847 nsCacheService::DoomCheckFn mCheckFn;
2850 void
2851 nsCacheService::DoomActiveEntries(DoomCheckFn check)
2853 nsAutoTArray<nsCacheEntry*, 8> array;
2854 ActiveEntryArgs args = { &array, check };
2856 mActiveEntries.VisitEntries(RemoveActiveEntry, &args);
2858 uint32_t count = array.Length();
2859 for (uint32_t i=0; i < count; ++i)
2860 DoomEntry_Internal(array[i], true);
2863 PLDHashOperator
2864 nsCacheService::RemoveActiveEntry(PLDHashTable * table,
2865 PLDHashEntryHdr * hdr,
2866 uint32_t number,
2867 void * arg)
2869 nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
2870 NS_ASSERTION(entry, "### active entry = nullptr!");
2872 ActiveEntryArgs* args = static_cast<ActiveEntryArgs*>(arg);
2873 if (args->mCheckFn && !args->mCheckFn(entry))
2874 return PL_DHASH_NEXT;
2876 NS_ASSERTION(args->mActiveArray, "### array = nullptr!");
2877 args->mActiveArray->AppendElement(entry);
2879 // entry is being removed from the active entry list
2880 entry->MarkInactive();
2881 return PL_DHASH_REMOVE; // and continue enumerating
2885 void
2886 nsCacheService::CloseAllStreams()
2888 nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
2889 nsTArray<nsRefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
2892 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
2894 nsVoidArray entries;
2896 #if DEBUG
2897 // make sure there is no active entry
2898 mActiveEntries.VisitEntries(GetActiveEntries, &entries);
2899 NS_ASSERTION(entries.Count() == 0, "Bad state");
2900 #endif
2902 // Get doomed entries
2903 nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
2904 while (entry != &mDoomedEntries) {
2905 nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
2906 entries.AppendElement(entry);
2907 entry = next;
2910 // Iterate through all entries and collect input and output streams
2911 for (int32_t i = 0 ; i < entries.Count() ; i++) {
2912 entry = static_cast<nsCacheEntry *>(entries.ElementAt(i));
2914 nsTArray<nsRefPtr<nsCacheEntryDescriptor> > descs;
2915 entry->GetDescriptors(descs);
2917 for (uint32_t j = 0 ; j < descs.Length() ; j++) {
2918 if (descs[j]->mOutputWrapper)
2919 outputs.AppendElement(descs[j]->mOutputWrapper);
2921 for (int32_t k = 0 ; k < descs[j]->mInputWrappers.Count() ; k++)
2922 inputs.AppendElement(static_cast<
2923 nsCacheEntryDescriptor::nsInputStreamWrapper *>(
2924 descs[j]->mInputWrappers[k]));
2929 uint32_t i;
2930 for (i = 0 ; i < inputs.Length() ; i++)
2931 inputs[i]->Close();
2933 for (i = 0 ; i < outputs.Length() ; i++)
2934 outputs[i]->Close();
2938 bool
2939 nsCacheService::GetClearingEntries()
2941 AssertOwnsLock();
2942 return gService->mClearingEntries;
2946 #if defined(PR_LOGGING)
2947 void
2948 nsCacheService::LogCacheStatistics()
2950 uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
2951 ((double)(mCacheHits + mCacheMisses))) * 100);
2952 CACHE_LOG_ALWAYS(("\nCache Service Statistics:\n\n"));
2953 CACHE_LOG_ALWAYS((" TotalEntries = %d\n", mTotalEntries));
2954 CACHE_LOG_ALWAYS((" Cache Hits = %d\n", mCacheHits));
2955 CACHE_LOG_ALWAYS((" Cache Misses = %d\n", mCacheMisses));
2956 CACHE_LOG_ALWAYS((" Cache Hit %% = %d%%\n", hitPercentage));
2957 CACHE_LOG_ALWAYS((" Max Key Length = %d\n", mMaxKeyLength));
2958 CACHE_LOG_ALWAYS((" Max Meta Size = %d\n", mMaxMetaSize));
2959 CACHE_LOG_ALWAYS((" Max Data Size = %d\n", mMaxDataSize));
2960 CACHE_LOG_ALWAYS(("\n"));
2961 CACHE_LOG_ALWAYS((" Deactivate Failures = %d\n",
2962 mDeactivateFailures));
2963 CACHE_LOG_ALWAYS((" Deactivated Unbound Entries = %d\n",
2964 mDeactivatedUnboundEntries));
2966 #endif
2968 nsresult
2969 nsCacheService::SetDiskSmartSize()
2971 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
2973 if (!gService) return NS_ERROR_NOT_AVAILABLE;
2975 return gService->SetDiskSmartSize_Locked();
2978 nsresult
2979 nsCacheService::SetDiskSmartSize_Locked()
2981 nsresult rv;
2983 if (!mObserver->DiskCacheParentDirectory())
2984 return NS_ERROR_NOT_AVAILABLE;
2986 if (!mDiskDevice)
2987 return NS_ERROR_NOT_AVAILABLE;
2989 if (!mObserver->SmartSizeEnabled())
2990 return NS_ERROR_NOT_AVAILABLE;
2992 nsAutoString cachePath;
2993 rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
2994 if (NS_SUCCEEDED(rv)) {
2995 nsCOMPtr<nsIRunnable> event =
2996 new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
2997 mObserver->ShouldUseOldMaxSmartSize());
2998 DispatchToCacheIOThread(event);
2999 } else {
3000 return NS_ERROR_FAILURE;
3003 return NS_OK;
3006 void
3007 nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
3008 nsIFile *aNewCacheDir,
3009 const char *aCacheSubdir)
3011 bool same;
3012 if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
3013 return;
3015 nsCOMPtr<nsIFile> aOldCacheSubdir;
3016 aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
3018 nsresult rv = aOldCacheSubdir->AppendNative(
3019 nsDependentCString(aCacheSubdir));
3020 if (NS_FAILED(rv))
3021 return;
3023 bool exists;
3024 if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
3025 return;
3027 nsCOMPtr<nsIFile> aNewCacheSubdir;
3028 aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
3030 rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
3031 if (NS_FAILED(rv))
3032 return;
3034 nsAutoCString newPath;
3035 rv = aNewCacheSubdir->GetNativePath(newPath);
3036 if (NS_FAILED(rv))
3037 return;
3039 if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
3040 // New cache directory does not exist, try to move the old one here
3041 // rename needs an empty target directory
3042 rv = aNewCacheSubdir->Create(nsIFile::DIRECTORY_TYPE, 0777);
3043 if (NS_SUCCEEDED(rv)) {
3044 nsAutoCString oldPath;
3045 rv = aOldCacheSubdir->GetNativePath(oldPath);
3046 if (NS_FAILED(rv))
3047 return;
3048 if(rename(oldPath.get(), newPath.get()) == 0)
3049 return;
3053 // Delay delete by 1 minute to avoid IO thrash on startup.
3054 nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
3057 static bool
3058 IsEntryPrivate(nsCacheEntry* entry)
3060 return entry->IsPrivate();
3063 void
3064 nsCacheService::LeavePrivateBrowsing()
3066 nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_LEAVEPRIVATEBROWSING));
3068 gService->DoomActiveEntries(IsEntryPrivate);
3070 if (gService->mMemoryDevice) {
3071 // clear memory cache
3072 gService->mMemoryDevice->EvictPrivateEntries();