summaryrefslogtreecommitdiffstats
path: root/netwerk/cache/nsCacheService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cache/nsCacheService.cpp')
-rw-r--r--netwerk/cache/nsCacheService.cpp3209
1 files changed, 3209 insertions, 0 deletions
diff --git a/netwerk/cache/nsCacheService.cpp b/netwerk/cache/nsCacheService.cpp
new file mode 100644
index 000000000..bab67e104
--- /dev/null
+++ b/netwerk/cache/nsCacheService.cpp
@@ -0,0 +1,3209 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+
+#include "necko-config.h"
+
+#include "nsCache.h"
+#include "nsCacheService.h"
+#include "nsCacheRequest.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheDevice.h"
+#include "nsMemoryCacheDevice.h"
+#include "nsICacheVisitor.h"
+#include "nsDiskCacheDevice.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheUtils.h"
+#include "../cache2/CacheObserver.h"
+
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIFile.h"
+#include "nsIOService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsDeleteDir.h"
+#include "nsNetCID.h"
+#include <math.h> // for log()
+#include "mozilla/Services.h"
+#include "nsITimer.h"
+#include "mozIStorageService.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+/******************************************************************************
+ * nsCacheProfilePrefObserver
+ *****************************************************************************/
+#define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
+#define DISK_CACHE_DIR_PREF "browser.cache.disk.parent_directory"
+#define DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF\
+ "browser.cache.disk.smart_size.first_run"
+#define DISK_CACHE_SMART_SIZE_ENABLED_PREF \
+ "browser.cache.disk.smart_size.enabled"
+#define DISK_CACHE_SMART_SIZE_PREF "browser.cache.disk.smart_size_cached_value"
+#define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
+#define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size"
+#define DISK_CACHE_CAPACITY 256000
+
+#define DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF \
+ "browser.cache.disk.smart_size.use_old_max"
+
+#define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable"
+#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
+#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
+#define OFFLINE_CACHE_CAPACITY 512000
+
+#define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
+#define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
+#define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size"
+
+#define CACHE_COMPRESSION_LEVEL_PREF "browser.cache.compression_level"
+#define CACHE_COMPRESSION_LEVEL 1
+
+#define SANITIZE_ON_SHUTDOWN_PREF "privacy.sanitize.sanitizeOnShutdown"
+#define CLEAR_ON_SHUTDOWN_PREF "privacy.clearOnShutdown.cache"
+
+static const char * observerList[] = {
+ "profile-before-change",
+ "profile-do-change",
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ "last-pb-context-exited",
+ "suspend_process_notification",
+ "resume_process_notification"
+};
+
+static const char * prefList[] = {
+ DISK_CACHE_ENABLE_PREF,
+ DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ DISK_CACHE_CAPACITY_PREF,
+ DISK_CACHE_DIR_PREF,
+ DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ OFFLINE_CACHE_ENABLE_PREF,
+ OFFLINE_CACHE_CAPACITY_PREF,
+ OFFLINE_CACHE_DIR_PREF,
+ MEMORY_CACHE_ENABLE_PREF,
+ MEMORY_CACHE_CAPACITY_PREF,
+ MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ CACHE_COMPRESSION_LEVEL_PREF,
+ SANITIZE_ON_SHUTDOWN_PREF,
+ CLEAR_ON_SHUTDOWN_PREF
+};
+
+// Cache sizes, in KB
+const int32_t DEFAULT_CACHE_SIZE = 250 * 1024; // 250 MB
+#ifdef ANDROID
+const int32_t MAX_CACHE_SIZE = 200 * 1024; // 200 MB
+const int32_t OLD_MAX_CACHE_SIZE = 200 * 1024; // 200 MB
+#else
+const int32_t MAX_CACHE_SIZE = 350 * 1024; // 350 MB
+const int32_t OLD_MAX_CACHE_SIZE = 1024 * 1024; // 1 GB
+#endif
+// Default cache size was 50 MB for many years until FF 4:
+const int32_t PRE_GECKO_2_0_DEFAULT_CACHE_SIZE = 50 * 1024;
+
+class nsCacheProfilePrefObserver : public nsIObserver
+{
+ virtual ~nsCacheProfilePrefObserver() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsCacheProfilePrefObserver()
+ : mHaveProfile(false)
+ , mDiskCacheEnabled(false)
+ , mDiskCacheCapacity(0)
+ , mDiskCacheMaxEntrySize(-1) // -1 means "no limit"
+ , mSmartSizeEnabled(false)
+ , mShouldUseOldMaxSmartSize(false)
+ , mOfflineCacheEnabled(false)
+ , mOfflineCacheCapacity(0)
+ , mMemoryCacheEnabled(true)
+ , mMemoryCacheCapacity(-1)
+ , mMemoryCacheMaxEntrySize(-1) // -1 means "no limit"
+ , mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL)
+ , mSanitizeOnShutdown(false)
+ , mClearCacheOnShutdown(false)
+ {
+ }
+
+ nsresult Install();
+ void Remove();
+ nsresult ReadPrefs(nsIPrefBranch* branch);
+
+ bool DiskCacheEnabled();
+ int32_t DiskCacheCapacity() { return mDiskCacheCapacity; }
+ void SetDiskCacheCapacity(int32_t);
+ int32_t DiskCacheMaxEntrySize() { return mDiskCacheMaxEntrySize; }
+ nsIFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
+ bool SmartSizeEnabled() { return mSmartSizeEnabled; }
+
+ bool ShouldUseOldMaxSmartSize() { return mShouldUseOldMaxSmartSize; }
+ void SetUseNewMaxSmartSize(bool useNew) { mShouldUseOldMaxSmartSize = !useNew; }
+
+ bool OfflineCacheEnabled();
+ int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
+ nsIFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; }
+
+ bool MemoryCacheEnabled();
+ int32_t MemoryCacheCapacity();
+ int32_t MemoryCacheMaxEntrySize() { return mMemoryCacheMaxEntrySize; }
+
+ int32_t CacheCompressionLevel();
+
+ bool SanitizeAtShutdown() { return mSanitizeOnShutdown && mClearCacheOnShutdown; }
+
+ static uint32_t GetSmartCacheSize(const nsAString& cachePath,
+ uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize);
+
+ bool PermittedToSmartSize(nsIPrefBranch*, bool firstRun);
+
+private:
+ bool mHaveProfile;
+
+ bool mDiskCacheEnabled;
+ int32_t mDiskCacheCapacity; // in kilobytes
+ int32_t mDiskCacheMaxEntrySize; // in kilobytes
+ nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
+ bool mSmartSizeEnabled;
+
+ bool mShouldUseOldMaxSmartSize;
+
+ bool mOfflineCacheEnabled;
+ int32_t mOfflineCacheCapacity; // in kilobytes
+ nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
+
+ bool mMemoryCacheEnabled;
+ int32_t mMemoryCacheCapacity; // in kilobytes
+ int32_t mMemoryCacheMaxEntrySize; // in kilobytes
+
+ int32_t mCacheCompressionLevel;
+
+ bool mSanitizeOnShutdown;
+ bool mClearCacheOnShutdown;
+};
+
+NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
+
+class nsSetDiskSmartSizeCallback final : public nsITimerCallback
+{
+ ~nsSetDiskSmartSizeCallback() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override {
+ if (nsCacheService::gService) {
+ nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSSETDISKSMARTSIZECALLBACK_NOTIFY));
+ nsCacheService::gService->SetDiskSmartSize_Locked();
+ nsCacheService::gService->mSmartSizeTimer = nullptr;
+ }
+ return NS_OK;
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsSetDiskSmartSizeCallback, nsITimerCallback)
+
+// Runnable sent to main thread after the cache IO thread calculates available
+// disk space, so that there is no race in setting mDiskCacheCapacity.
+class nsSetSmartSizeEvent: public Runnable
+{
+public:
+ explicit nsSetSmartSizeEvent(int32_t smartSize)
+ : mSmartSize(smartSize) {}
+
+ NS_IMETHOD Run()
+ {
+ NS_ASSERTION(NS_IsMainThread(),
+ "Setting smart size data off the main thread");
+
+ // Main thread may have already called nsCacheService::Shutdown
+ if (!nsCacheService::IsInitialized())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Ensure smart sizing wasn't switched off while event was pending.
+ // It is safe to access the observer without the lock since we are
+ // on the main thread and the value changes only on the main thread.
+ if (!nsCacheService::gService->mObserver->SmartSizeEnabled())
+ return NS_OK;
+
+ nsCacheService::SetDiskCacheCapacity(mSmartSize);
+
+ nsCOMPtr<nsIPrefBranch> ps = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!ps ||
+ NS_FAILED(ps->SetIntPref(DISK_CACHE_SMART_SIZE_PREF, mSmartSize)))
+ NS_WARNING("Failed to set smart size pref");
+
+ return NS_OK;
+ }
+
+private:
+ int32_t mSmartSize;
+};
+
+
+// Runnable sent from main thread to cacheIO thread
+class nsGetSmartSizeEvent: public Runnable
+{
+public:
+ nsGetSmartSizeEvent(const nsAString& cachePath, uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize)
+ : mCachePath(cachePath)
+ , mCurrentSize(currentSize)
+ , mShouldUseOldMaxSmartSize(shouldUseOldMaxSmartSize)
+ {}
+
+ // Calculates user's disk space available on a background thread and
+ // dispatches this value back to the main thread.
+ NS_IMETHOD Run() override
+ {
+ uint32_t size;
+ size = nsCacheProfilePrefObserver::GetSmartCacheSize(mCachePath,
+ mCurrentSize,
+ mShouldUseOldMaxSmartSize);
+ NS_DispatchToMainThread(new nsSetSmartSizeEvent(size));
+ return NS_OK;
+ }
+
+private:
+ nsString mCachePath;
+ uint32_t mCurrentSize;
+ bool mShouldUseOldMaxSmartSize;
+};
+
+class nsBlockOnCacheThreadEvent : public Runnable {
+public:
+ nsBlockOnCacheThreadEvent()
+ {
+ }
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
+ CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
+ nsCacheService::gService->mNotified = true;
+ nsCacheService::gService->mCondVar.Notify();
+ return NS_OK;
+ }
+};
+
+
+nsresult
+nsCacheProfilePrefObserver::Install()
+{
+ // install profile-change observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv, rv2 = NS_OK;
+ for (unsigned int i=0; i<ArrayLength(observerList); i++) {
+ rv = observerService->AddObserver(this, observerList[i], false);
+ if (NS_FAILED(rv))
+ rv2 = rv;
+ }
+
+ // install preferences observer
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) return NS_ERROR_FAILURE;
+
+ for (unsigned int i=0; i<ArrayLength(prefList); i++) {
+ rv = branch->AddObserver(prefList[i], this, false);
+ if (NS_FAILED(rv))
+ rv2 = rv;
+ }
+
+ // Determine if we have a profile already
+ // Install() is called *after* the profile-after-change notification
+ // when there is only a single profile, or it is specified on the
+ // commandline at startup.
+ // In that case, we detect the presence of a profile by the existence
+ // of the NS_APP_USER_PROFILE_50_DIR directory.
+
+ nsCOMPtr<nsIFile> directory;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(directory));
+ if (NS_SUCCEEDED(rv))
+ mHaveProfile = true;
+
+ rv = ReadPrefs(branch);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv2;
+}
+
+
+void
+nsCacheProfilePrefObserver::Remove()
+{
+ // remove Observer Service observers
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ if (obs) {
+ for (unsigned int i=0; i<ArrayLength(observerList); i++) {
+ obs->RemoveObserver(this, observerList[i]);
+ }
+ }
+
+ // remove Pref Service observers
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return;
+ for (unsigned int i=0; i<ArrayLength(prefList); i++)
+ prefs->RemoveObserver(prefList[i], this); // remove cache pref observers
+}
+
+void
+nsCacheProfilePrefObserver::SetDiskCacheCapacity(int32_t capacity)
+{
+ mDiskCacheCapacity = std::max(0, capacity);
+}
+
+
+NS_IMETHODIMP
+nsCacheProfilePrefObserver::Observe(nsISupports * subject,
+ const char * topic,
+ const char16_t * data_unicode)
+{
+ nsresult rv;
+ NS_ConvertUTF16toUTF8 data(data_unicode);
+ CACHE_LOG_INFO(("Observe [topic=%s data=%s]\n", topic, data.get()));
+
+ if (!nsCacheService::IsInitialized()) {
+ if (!strcmp("resume_process_notification", topic)) {
+ // A suspended process has a closed cache, so re-open it here.
+ nsCacheService::GlobalInstance()->Init();
+ }
+ return NS_OK;
+ }
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ // xpcom going away, shutdown cache service
+ nsCacheService::GlobalInstance()->Shutdown();
+ } else if (!strcmp("profile-before-change", topic)) {
+ // profile before change
+ mHaveProfile = false;
+
+ // XXX shutdown devices
+ nsCacheService::OnProfileShutdown();
+ } else if (!strcmp("suspend_process_notification", topic)) {
+ // A suspended process may never return, so shutdown the cache to reduce
+ // cache corruption.
+ nsCacheService::GlobalInstance()->Shutdown();
+ } else if (!strcmp("profile-do-change", topic)) {
+ // profile after change
+ mHaveProfile = true;
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) {
+ return NS_ERROR_FAILURE;
+ }
+ (void)ReadPrefs(branch);
+ nsCacheService::OnProfileChanged();
+
+ } else if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, topic)) {
+
+ // ignore pref changes until we're done switch profiles
+ if (!mHaveProfile)
+ return NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(subject, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // which preference changed?
+ if (!strcmp(DISK_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(DISK_CACHE_ENABLE_PREF,
+ &mDiskCacheEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+
+ } else if (!strcmp(DISK_CACHE_CAPACITY_PREF, data.get())) {
+
+ int32_t capacity = 0;
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
+ if (NS_FAILED(rv))
+ return rv;
+ mDiskCacheCapacity = std::max(0, capacity);
+ nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
+
+ // Update the cache capacity when smart sizing is turned on/off
+ } else if (!strcmp(DISK_CACHE_SMART_SIZE_ENABLED_PREF, data.get())) {
+ // Is the update because smartsizing was turned on, or off?
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ &mSmartSizeEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ int32_t newCapacity = 0;
+ if (mSmartSizeEnabled) {
+ nsCacheService::SetDiskSmartSize();
+ } else {
+ // Smart sizing switched off: use user specified size
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &newCapacity);
+ if (NS_FAILED(rv))
+ return rv;
+ mDiskCacheCapacity = std::max(0, newCapacity);
+ nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
+ }
+ } else if (!strcmp(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, data.get())) {
+ rv = branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ &mShouldUseOldMaxSmartSize);
+ if (NS_FAILED(rv))
+ return rv;
+ } else if (!strcmp(DISK_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+ int32_t newMaxSize;
+ rv = branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ &newMaxSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDiskCacheMaxEntrySize = std::max(-1, newMaxSize);
+ nsCacheService::SetDiskCacheMaxEntrySize(mDiskCacheMaxEntrySize);
+
+#if 0
+ } else if (!strcmp(DISK_CACHE_DIR_PREF, data.get())) {
+ // XXX We probaby don't want to respond to this pref except after
+ // XXX profile changes. Ideally, there should be somekind of user
+ // XXX notification that the pref change won't take effect until
+ // XXX the next time the profile changes (browser launch)
+#endif
+ } else
+
+ // which preference changed?
+ if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
+ &mOfflineCacheEnabled);
+ if (NS_FAILED(rv)) return rv;
+ nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled());
+
+ } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) {
+
+ int32_t capacity = 0;
+ rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity);
+ if (NS_FAILED(rv)) return rv;
+ mOfflineCacheCapacity = std::max(0, capacity);
+ nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity);
+#if 0
+ } else if (!strcmp(OFFLINE_CACHE_DIR_PREF, data.get())) {
+ // XXX We probaby don't want to respond to this pref except after
+ // XXX profile changes. Ideally, there should be some kind of user
+ // XXX notification that the pref change won't take effect until
+ // XXX the next time the profile changes (browser launch)
+#endif
+ } else
+
+ if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) {
+
+ rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF,
+ &mMemoryCacheEnabled);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetMemoryCache();
+
+ } else if (!strcmp(MEMORY_CACHE_CAPACITY_PREF, data.get())) {
+
+ mMemoryCacheCapacity = -1;
+ (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
+ &mMemoryCacheCapacity);
+ nsCacheService::SetMemoryCache();
+ } else if (!strcmp(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF, data.get())) {
+ int32_t newMaxSize;
+ rv = branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ &newMaxSize);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mMemoryCacheMaxEntrySize = std::max(-1, newMaxSize);
+ nsCacheService::SetMemoryCacheMaxEntrySize(mMemoryCacheMaxEntrySize);
+ } else if (!strcmp(CACHE_COMPRESSION_LEVEL_PREF, data.get())) {
+ mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
+ (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
+ &mCacheCompressionLevel);
+ mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
+ mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
+ } else if (!strcmp(SANITIZE_ON_SHUTDOWN_PREF, data.get())) {
+ rv = branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
+ &mSanitizeOnShutdown);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+ } else if (!strcmp(CLEAR_ON_SHUTDOWN_PREF, data.get())) {
+ rv = branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
+ &mClearCacheOnShutdown);
+ if (NS_FAILED(rv))
+ return rv;
+ nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
+ }
+ } else if (!strcmp("last-pb-context-exited", topic)) {
+ nsCacheService::LeavePrivateBrowsing();
+ }
+
+ return NS_OK;
+}
+
+// Returns default ("smart") size (in KB) of cache, given available disk space
+// (also in KB)
+static uint32_t
+SmartCacheSize(const uint32_t availKB, bool shouldUseOldMaxSmartSize)
+{
+ uint32_t maxSize = shouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
+
+ if (availKB > 100 * 1024 * 1024)
+ return maxSize; // skip computing if we're over 100 GB
+
+ // Grow/shrink in 10 MB units, deliberately, so that in the common case we
+ // don't shrink cache and evict items every time we startup (it's important
+ // that we don't slow down startup benchmarks).
+ uint32_t sz10MBs = 0;
+ uint32_t avail10MBs = availKB / (1024*10);
+
+ // .5% of space above 25 GB
+ if (avail10MBs > 2500) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 2500)*.005);
+ avail10MBs = 2500;
+ }
+ // 1% of space between 7GB -> 25 GB
+ if (avail10MBs > 700) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 700)*.01);
+ avail10MBs = 700;
+ }
+ // 5% of space between 500 MB -> 7 GB
+ if (avail10MBs > 50) {
+ sz10MBs += static_cast<uint32_t>((avail10MBs - 50)*.05);
+ avail10MBs = 50;
+ }
+
+#ifdef ANDROID
+ // On Android, smaller/older devices may have very little storage and
+ // device owners may be sensitive to storage footprint: Use a smaller
+ // percentage of available space and a smaller minimum.
+
+ // 20% of space up to 500 MB (10 MB min)
+ sz10MBs += std::max<uint32_t>(1, static_cast<uint32_t>(avail10MBs * .2));
+#else
+ // 40% of space up to 500 MB (50 MB min)
+ sz10MBs += std::max<uint32_t>(5, static_cast<uint32_t>(avail10MBs * .4));
+#endif
+
+ return std::min<uint32_t>(maxSize, sz10MBs * 10 * 1024);
+}
+
+ /* Computes our best guess for the default size of the user's disk cache,
+ * based on the amount of space they have free on their hard drive.
+ * We use a tiered scheme: the more space available,
+ * the larger the disk cache will be. However, we do not want
+ * to enable the disk cache to grow to an unbounded size, so the larger the
+ * user's available space is, the smaller of a percentage we take. We set a
+ * lower bound of 50MB and an upper bound of 1GB.
+ *
+ *@param: None.
+ *@return: The size that the user's disk cache should default to, in kBytes.
+ */
+uint32_t
+nsCacheProfilePrefObserver::GetSmartCacheSize(const nsAString& cachePath,
+ uint32_t currentSize,
+ bool shouldUseOldMaxSmartSize)
+{
+ // Check for free space on device where cache directory lives
+ nsresult rv;
+ nsCOMPtr<nsIFile>
+ cacheDirectory (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv) || !cacheDirectory)
+ return DEFAULT_CACHE_SIZE;
+ rv = cacheDirectory->InitWithPath(cachePath);
+ if (NS_FAILED(rv))
+ return DEFAULT_CACHE_SIZE;
+ int64_t bytesAvailable;
+ rv = cacheDirectory->GetDiskSpaceAvailable(&bytesAvailable);
+ if (NS_FAILED(rv))
+ return DEFAULT_CACHE_SIZE;
+
+ return SmartCacheSize(static_cast<uint32_t>((bytesAvailable / 1024) +
+ currentSize),
+ shouldUseOldMaxSmartSize);
+}
+
+/* Determine if we are permitted to dynamically size the user's disk cache based
+ * on their disk space available. We may do this so long as the pref
+ * smart_size.enabled is true.
+ */
+bool
+nsCacheProfilePrefObserver::PermittedToSmartSize(nsIPrefBranch* branch, bool
+ firstRun)
+{
+ nsresult rv;
+ if (firstRun) {
+ // check if user has set cache size in the past
+ bool userSet;
+ rv = branch->PrefHasUserValue(DISK_CACHE_CAPACITY_PREF, &userSet);
+ if (NS_FAILED(rv)) userSet = true;
+ if (userSet) {
+ int32_t oldCapacity;
+ // If user explicitly set cache size to be smaller than old default
+ // of 50 MB, then keep user's value. Otherwise use smart sizing.
+ rv = branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &oldCapacity);
+ if (oldCapacity < PRE_GECKO_2_0_DEFAULT_CACHE_SIZE) {
+ mSmartSizeEnabled = false;
+ branch->SetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ mSmartSizeEnabled);
+ return mSmartSizeEnabled;
+ }
+ }
+ // Set manual setting to MAX cache size as starting val for any
+ // adjustment by user: (bug 559942 comment 65)
+ int32_t maxSize = mShouldUseOldMaxSmartSize ? OLD_MAX_CACHE_SIZE : MAX_CACHE_SIZE;
+ branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, maxSize);
+ }
+
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_ENABLED_PREF,
+ &mSmartSizeEnabled);
+ if (NS_FAILED(rv))
+ mSmartSizeEnabled = false;
+ return mSmartSizeEnabled;
+}
+
+
+nsresult
+nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch)
+{
+ nsresult rv = NS_OK;
+
+ // read disk cache device prefs
+ mDiskCacheEnabled = true; // presume disk cache is enabled
+ (void) branch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
+
+ mDiskCacheCapacity = DISK_CACHE_CAPACITY;
+ (void)branch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &mDiskCacheCapacity);
+ mDiskCacheCapacity = std::max(0, mDiskCacheCapacity);
+
+ (void) branch->GetIntPref(DISK_CACHE_MAX_ENTRY_SIZE_PREF,
+ &mDiskCacheMaxEntrySize);
+ mDiskCacheMaxEntrySize = std::max(-1, mDiskCacheMaxEntrySize);
+
+ (void) branch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(mDiskCacheParentDirectory));
+
+ (void) branch->GetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF,
+ &mShouldUseOldMaxSmartSize);
+
+ if (!mDiskCacheParentDirectory) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the disk cache parent directory
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ // try to get the profile directory (there may not be a profile yet)
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ if (!directory)
+ directory = profDir;
+ else if (profDir) {
+ nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
+ "Cache");
+ }
+ }
+ // use file cache in build tree only if asked, to avoid cache dir litter
+ if (!directory && PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE")) {
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+ if (directory)
+ mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
+ }
+ if (mDiskCacheParentDirectory) {
+ bool firstSmartSizeRun;
+ rv = branch->GetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
+ &firstSmartSizeRun);
+ if (NS_FAILED(rv))
+ firstSmartSizeRun = false;
+ if (PermittedToSmartSize(branch, firstSmartSizeRun)) {
+ // Avoid evictions: use previous cache size until smart size event
+ // updates mDiskCacheCapacity
+ rv = branch->GetIntPref(firstSmartSizeRun ?
+ DISK_CACHE_CAPACITY_PREF :
+ DISK_CACHE_SMART_SIZE_PREF,
+ &mDiskCacheCapacity);
+ if (NS_FAILED(rv))
+ mDiskCacheCapacity = DEFAULT_CACHE_SIZE;
+ }
+
+ if (firstSmartSizeRun) {
+ // It is no longer our first run
+ rv = branch->SetBoolPref(DISK_CACHE_SMART_SIZE_FIRST_RUN_PREF,
+ false);
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed setting first_run pref in ReadPrefs.");
+ }
+ }
+
+ // read offline cache device prefs
+ mOfflineCacheEnabled = true; // presume offline cache is enabled
+ (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF,
+ &mOfflineCacheEnabled);
+
+ mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
+ (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF,
+ &mOfflineCacheCapacity);
+ mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
+
+ (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(mOfflineCacheParentDirectory));
+
+ if (!mOfflineCacheParentDirectory) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the offline cache parent directory
+ rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ // try to get the profile directory (there may not be a profile yet)
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ if (!directory)
+ directory = profDir;
+ else if (profDir) {
+ nsCacheService::MoveOrRemoveDiskCache(profDir, directory,
+ "OfflineCache");
+ }
+ }
+#if DEBUG
+ if (!directory) {
+ // use current process directory during development
+ rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+#endif
+ if (directory)
+ mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv);
+ }
+
+ // read memory cache device prefs
+ (void) branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
+
+ mMemoryCacheCapacity = -1;
+ (void) branch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF,
+ &mMemoryCacheCapacity);
+
+ (void) branch->GetIntPref(MEMORY_CACHE_MAX_ENTRY_SIZE_PREF,
+ &mMemoryCacheMaxEntrySize);
+ mMemoryCacheMaxEntrySize = std::max(-1, mMemoryCacheMaxEntrySize);
+
+ // read cache compression level pref
+ mCacheCompressionLevel = CACHE_COMPRESSION_LEVEL;
+ (void)branch->GetIntPref(CACHE_COMPRESSION_LEVEL_PREF,
+ &mCacheCompressionLevel);
+ mCacheCompressionLevel = std::max(0, mCacheCompressionLevel);
+ mCacheCompressionLevel = std::min(9, mCacheCompressionLevel);
+
+ // read cache shutdown sanitization prefs
+ (void) branch->GetBoolPref(SANITIZE_ON_SHUTDOWN_PREF,
+ &mSanitizeOnShutdown);
+ (void) branch->GetBoolPref(CLEAR_ON_SHUTDOWN_PREF,
+ &mClearCacheOnShutdown);
+
+ return rv;
+}
+
+nsresult
+nsCacheService::DispatchToCacheIOThread(nsIRunnable* event)
+{
+ if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+ return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+nsresult
+nsCacheService::SyncWithCacheIOThread()
+{
+ if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+ gService->mLock.AssertCurrentThreadOwns();
+
+ nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
+
+ // dispatch event - it will notify the monitor when it's done
+ nsresult rv =
+ gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed dispatching block-event");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // wait until notified, then return
+ gService->mNotified = false;
+ while (!gService->mNotified) {
+ gService->mCondVar.Wait();
+ }
+
+ return NS_OK;
+}
+
+
+bool
+nsCacheProfilePrefObserver::DiskCacheEnabled()
+{
+ if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return false;
+ return mDiskCacheEnabled && (!mSanitizeOnShutdown || !mClearCacheOnShutdown);
+}
+
+
+bool
+nsCacheProfilePrefObserver::OfflineCacheEnabled()
+{
+ if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
+ return false;
+
+ return mOfflineCacheEnabled;
+}
+
+
+bool
+nsCacheProfilePrefObserver::MemoryCacheEnabled()
+{
+ if (mMemoryCacheCapacity == 0) return false;
+ return mMemoryCacheEnabled;
+}
+
+
+/**
+ * MemoryCacheCapacity
+ *
+ * If the browser.cache.memory.capacity preference is positive, we use that
+ * value for the amount of memory available for the cache.
+ *
+ * If browser.cache.memory.capacity is zero, the memory cache is disabled.
+ *
+ * If browser.cache.memory.capacity is negative or not present, we use a
+ * formula that grows less than linearly with the amount of system memory,
+ * with an upper limit on the cache size. No matter how much physical RAM is
+ * present, the default cache size would not exceed 32 MB. This maximum would
+ * apply only to systems with more than 4 GB of RAM (e.g. terminal servers)
+ *
+ * RAM Cache
+ * --- -----
+ * 32 Mb 2 Mb
+ * 64 Mb 4 Mb
+ * 128 Mb 6 Mb
+ * 256 Mb 10 Mb
+ * 512 Mb 14 Mb
+ * 1024 Mb 18 Mb
+ * 2048 Mb 24 Mb
+ * 4096 Mb 30 Mb
+ *
+ * The equation for this is (for cache size C and memory size K (kbytes)):
+ * x = log2(K) - 14
+ * C = x^2/3 + x + 2/3 + 0.1 (0.1 for rounding)
+ * if (C > 32) C = 32
+ */
+
+int32_t
+nsCacheProfilePrefObserver::MemoryCacheCapacity()
+{
+ int32_t capacity = mMemoryCacheCapacity;
+ if (capacity >= 0) {
+ CACHE_LOG_DEBUG(("Memory cache capacity forced to %d\n", capacity));
+ return capacity;
+ }
+
+ static uint64_t bytes = PR_GetPhysicalMemorySize();
+ CACHE_LOG_DEBUG(("Physical Memory size is %llu\n", bytes));
+
+ // If getting the physical memory failed, arbitrarily assume
+ // 32 MB of RAM. We use a low default to have a reasonable
+ // size on all the devices we support.
+ if (bytes == 0)
+ bytes = 32 * 1024 * 1024;
+
+ // Conversion from unsigned int64_t to double doesn't work on all platforms.
+ // We need to truncate the value at INT64_MAX to make sure we don't
+ // overflow.
+ if (bytes > INT64_MAX)
+ bytes = INT64_MAX;
+
+ uint64_t kbytes = bytes >> 10;
+
+ double kBytesD = double(kbytes);
+
+ double x = log(kBytesD)/log(2.0) - 14;
+ if (x > 0) {
+ capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding
+ if (capacity > 32)
+ capacity = 32;
+ capacity *= 1024;
+ } else {
+ capacity = 0;
+ }
+
+ return capacity;
+}
+
+int32_t
+nsCacheProfilePrefObserver::CacheCompressionLevel()
+{
+ return mCacheCompressionLevel;
+}
+
+/******************************************************************************
+ * nsProcessRequestEvent
+ *****************************************************************************/
+
+class nsProcessRequestEvent : public Runnable {
+public:
+ explicit nsProcessRequestEvent(nsCacheRequest *aRequest)
+ {
+ mRequest = aRequest;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsresult rv;
+
+ NS_ASSERTION(mRequest->mListener,
+ "Sync OpenCacheEntry() posted to background thread!");
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
+ rv = nsCacheService::gService->ProcessRequest(mRequest,
+ false,
+ nullptr);
+
+ // Don't delete the request if it was queued
+ if (!(mRequest->IsBlocking() &&
+ rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
+ delete mRequest;
+
+ return NS_OK;
+ }
+
+protected:
+ virtual ~nsProcessRequestEvent() {}
+
+private:
+ nsCacheRequest *mRequest;
+};
+
+/******************************************************************************
+ * nsDoomEvent
+ *****************************************************************************/
+
+class nsDoomEvent : public Runnable {
+public:
+ nsDoomEvent(nsCacheSession *session,
+ const nsACString &key,
+ nsICacheListener *listener)
+ {
+ mKey = *session->ClientID();
+ mKey.Append(':');
+ mKey.Append(key);
+ mStoragePolicy = session->StoragePolicy();
+ mListener = listener;
+ mThread = do_GetCurrentThread();
+ // We addref the listener here and release it in nsNotifyDoomListener
+ // on the callers thread. If posting of nsNotifyDoomListener event fails
+ // we leak the listener which is better than releasing it on a wrong
+ // thread.
+ NS_IF_ADDREF(mListener);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCacheServiceAutoLock lock;
+
+ bool foundActive = true;
+ nsresult status = NS_ERROR_NOT_AVAILABLE;
+ nsCacheEntry *entry;
+ entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
+ if (!entry) {
+ bool collision = false;
+ foundActive = false;
+ entry = nsCacheService::gService->SearchCacheDevices(&mKey,
+ mStoragePolicy,
+ &collision);
+ }
+
+ if (entry) {
+ status = NS_OK;
+ nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
+ }
+
+ if (mListener) {
+ mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
+ NS_DISPATCH_NORMAL);
+ // posted event will release the reference on the correct thread
+ mListener = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCString mKey;
+ nsCacheStoragePolicy mStoragePolicy;
+ nsICacheListener *mListener;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+/******************************************************************************
+ * nsCacheService
+ *****************************************************************************/
+nsCacheService * nsCacheService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal,
+ nsIMemoryReporter)
+
+nsCacheService::nsCacheService()
+ : mObserver(nullptr),
+ mLock("nsCacheService.mLock"),
+ mCondVar(mLock, "nsCacheService.mCondVar"),
+ mNotified(false),
+ mTimeStampLock("nsCacheService.mTimeStampLock"),
+ mInitialized(false),
+ mClearingEntries(false),
+ mEnableMemoryDevice(true),
+ mEnableDiskDevice(true),
+ mMemoryDevice(nullptr),
+ mDiskDevice(nullptr),
+ mOfflineDevice(nullptr),
+ mTotalEntries(0),
+ mCacheHits(0),
+ mCacheMisses(0),
+ mMaxKeyLength(0),
+ mMaxDataSize(0),
+ mMaxMetaSize(0),
+ mDeactivateFailures(0),
+ mDeactivatedUnboundEntries(0)
+{
+ NS_ASSERTION(gService==nullptr, "multiple nsCacheService instances!");
+ gService = this;
+
+ // create list of cache devices
+ PR_INIT_CLIST(&mDoomedEntries);
+}
+
+nsCacheService::~nsCacheService()
+{
+ if (mInitialized) // Shutdown hasn't been called yet.
+ (void) Shutdown();
+
+ if (mObserver) {
+ mObserver->Remove();
+ NS_RELEASE(mObserver);
+ }
+
+ gService = nullptr;
+}
+
+
+nsresult
+nsCacheService::Init()
+{
+ // Thie method must be called on the main thread because mCacheIOThread must
+ // only be modified on the main thread.
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsCacheService::Init called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
+ if (mInitialized)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ if (mozilla::net::IsNeckoChild()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+
+ mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewNamedThread("Cache I/O",
+ getter_AddRefs(mCacheIOThread));
+ if (NS_FAILED(rv)) {
+ NS_RUNTIMEABORT("Can't create cache IO thread");
+ }
+
+ rv = nsDeleteDir::Init();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't initialize nsDeleteDir");
+ }
+
+ // initialize hashtable for active cache entries
+ mActiveEntries.Init();
+
+ // create profile/preference observer
+ if (!mObserver) {
+ mObserver = new nsCacheProfilePrefObserver();
+ NS_ADDREF(mObserver);
+ mObserver->Install();
+ }
+
+ mEnableDiskDevice = mObserver->DiskCacheEnabled();
+ mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
+ mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
+
+ RegisterWeakMemoryReporter(this);
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+void
+nsCacheService::Shutdown()
+{
+ // This method must be called on the main thread because mCacheIOThread must
+ // only be modified on the main thread.
+ if (!NS_IsMainThread()) {
+ NS_RUNTIMEABORT("nsCacheService::Shutdown called off the main thread");
+ }
+
+ nsCOMPtr<nsIThread> cacheIOThread;
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
+
+ bool shouldSanitize = false;
+ nsCOMPtr<nsIFile> parentDir;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(mInitialized,
+ "can't shutdown nsCacheService unless it has been initialized.");
+ if (!mInitialized)
+ return;
+
+ mClearingEntries = true;
+ DoomActiveEntries(nullptr);
+ }
+
+ CloseAllStreams();
+
+ UnregisterWeakMemoryReporter(this);
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(mInitialized, "Bad state");
+
+ mInitialized = false;
+
+ // Clear entries
+ ClearDoomList();
+
+ if (mSmartSizeTimer) {
+ mSmartSizeTimer->Cancel();
+ mSmartSizeTimer = nullptr;
+ }
+
+ // Make sure to wait for any pending cache-operations before
+ // proceeding with destructive actions (bug #620660)
+ (void) SyncWithCacheIOThread();
+ mActiveEntries.Shutdown();
+
+ // obtain the disk cache directory in case we need to sanitize it
+ parentDir = mObserver->DiskCacheParentDirectory();
+ shouldSanitize = mObserver->SanitizeAtShutdown();
+
+ // deallocate memory and disk caches
+ delete mMemoryDevice;
+ mMemoryDevice = nullptr;
+
+ delete mDiskDevice;
+ mDiskDevice = nullptr;
+
+ if (mOfflineDevice)
+ mOfflineDevice->Shutdown();
+
+ NS_IF_RELEASE(mOfflineDevice);
+
+ for (auto iter = mCustomOfflineDevices.Iter();
+ !iter.Done(); iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+
+ LogCacheStatistics();
+
+ mClearingEntries = false;
+ mCacheIOThread.swap(cacheIOThread);
+ }
+
+ if (cacheIOThread)
+ nsShutdownThread::BlockingShutdown(cacheIOThread);
+
+ if (shouldSanitize) {
+ nsresult rv = parentDir->AppendNative(NS_LITERAL_CSTRING("Cache"));
+ if (NS_SUCCEEDED(rv)) {
+ bool exists;
+ if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
+ nsDeleteDir::DeleteDir(parentDir, false);
+ }
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE> timer;
+ nsDeleteDir::Shutdown(shouldSanitize);
+ } else {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN> timer;
+ nsDeleteDir::Shutdown(shouldSanitize);
+ }
+}
+
+
+nsresult
+nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
+{
+ nsresult rv;
+
+ if (aOuter != nullptr)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsCacheService * cacheService = new nsCacheService();
+ if (cacheService == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(cacheService);
+ rv = cacheService->Init();
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheService->QueryInterface(aIID, aResult);
+ }
+ NS_RELEASE(cacheService);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheService::CreateSession(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased,
+ nsICacheSession **result)
+{
+ *result = nullptr;
+
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return CreateSessionInternal(clientID, storagePolicy, streamBased, result);
+}
+
+nsresult
+nsCacheService::CreateSessionInternal(const char * clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased,
+ nsICacheSession **result)
+{
+ RefPtr<nsCacheSession> session =
+ new nsCacheSession(clientID, storagePolicy, streamBased);
+ session.forget(result);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheService::EvictEntriesForSession(nsCacheSession * session)
+{
+ NS_ASSERTION(gService, "nsCacheService::gService is null.");
+ return gService->EvictEntriesForClient(session->ClientID()->get(),
+ session->StoragePolicy());
+}
+
+namespace {
+
+class EvictionNotifierRunnable : public Runnable
+{
+public:
+ explicit EvictionNotifierRunnable(nsISupports* aSubject)
+ : mSubject(aSubject)
+ { }
+
+ NS_DECL_NSIRUNNABLE
+
+private:
+ nsCOMPtr<nsISupports> mSubject;
+};
+
+NS_IMETHODIMP
+EvictionNotifierRunnable::Run()
+{
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(mSubject,
+ NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
+ nullptr);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+nsresult
+nsCacheService::EvictEntriesForClient(const char * clientID,
+ nsCacheStoragePolicy storagePolicy)
+{
+ RefPtr<EvictionNotifierRunnable> r =
+ new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
+ NS_DispatchToMainThread(r);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
+ nsresult res = NS_OK;
+
+ if (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_ON_DISK) {
+
+ if (mEnableDiskDevice) {
+ nsresult rv = NS_OK;
+ if (!mDiskDevice)
+ rv = CreateDiskDevice();
+ if (mDiskDevice)
+ rv = mDiskDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ // Only clear the offline cache if it has been specifically asked for.
+ if (storagePolicy == nsICache::STORE_OFFLINE) {
+ if (mEnableOfflineDevice) {
+ nsresult rv = NS_OK;
+ if (!mOfflineDevice)
+ rv = CreateOfflineDevice();
+ if (mOfflineDevice)
+ rv = mOfflineDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ if (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_IN_MEMORY) {
+ // If there is no memory device, there is no need to evict it...
+ if (mMemoryDevice) {
+ nsresult rv = mMemoryDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv))
+ res = rv;
+ }
+ }
+
+ return res;
+}
+
+
+nsresult
+nsCacheService::IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
+ bool * result)
+{
+ if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
+
+ *result = gService->IsStorageEnabledForPolicy_Locked(storagePolicy);
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheService::DoomEntry(nsCacheSession *session,
+ const nsACString &key,
+ nsICacheListener *listener)
+{
+ CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n",
+ session, PromiseFlatCString(key).get()));
+ if (!gService || !gService->mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
+}
+
+
+bool
+nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePolicy)
+{
+ if (gService->mEnableMemoryDevice &&
+ (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_IN_MEMORY)) {
+ return true;
+ }
+ if (gService->mEnableDiskDevice &&
+ (storagePolicy == nsICache::STORE_ANYWHERE ||
+ storagePolicy == nsICache::STORE_ON_DISK)) {
+ return true;
+ }
+ if (gService->mEnableOfflineDevice &&
+ storagePolicy == nsICache::STORE_OFFLINE) {
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
+{
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return VisitEntriesInternal(visitor);
+}
+
+nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor *visitor)
+{
+ NS_ENSURE_ARG_POINTER(visitor);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
+
+ if (!(mEnableDiskDevice || mEnableMemoryDevice))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX record the fact that a visitation is in progress,
+ // XXX i.e. keep list of visitors in progress.
+
+ nsresult rv = NS_OK;
+ // If there is no memory device, there are then also no entries to visit...
+ if (mMemoryDevice) {
+ rv = mMemoryDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mEnableDiskDevice) {
+ if (!mDiskDevice) {
+ rv = CreateDiskDevice();
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = mDiskDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ rv = CreateOfflineDevice();
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = mOfflineDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // XXX notify any shutdown process that visitation is complete for THIS visitor.
+ // XXX keep queue of visitors
+
+ return NS_OK;
+}
+
+void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
+ if (obsvc) {
+ obsvc->NotifyObservers(nullptr,
+ "network-clear-cache-stored-anywhere",
+ nullptr);
+ }
+}
+
+NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
+{
+ if (net::CacheObserver::UseNewCache())
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+ return EvictEntriesInternal(storagePolicy);
+}
+
+nsresult nsCacheService::EvictEntriesInternal(nsCacheStoragePolicy storagePolicy)
+{
+ if (storagePolicy == nsICache::STORE_ANYWHERE) {
+ // if not called on main thread, dispatch the notification to the main thread to notify observers
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(this,
+ &nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
+ NS_DispatchToMainThread(event);
+ } else {
+ // else you're already on main thread - notify observers
+ FireClearNetworkCacheStoredAnywhereNotification();
+ }
+ }
+ return EvictEntriesForClient(nullptr, storagePolicy);
+}
+
+NS_IMETHODIMP nsCacheService::GetCacheIOTarget(nsIEventTarget * *aCacheIOTarget)
+{
+ NS_ENSURE_ARG_POINTER(aCacheIOTarget);
+
+ // Because mCacheIOThread can only be changed on the main thread, it can be
+ // read from the main thread without the lock. This is useful to prevent
+ // blocking the main thread on other cache operations.
+ if (!NS_IsMainThread()) {
+ Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
+ }
+
+ nsresult rv;
+ if (mCacheIOThread) {
+ NS_ADDREF(*aCacheIOTarget = mCacheIOThread);
+ rv = NS_OK;
+ } else {
+ *aCacheIOTarget = nullptr;
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!NS_IsMainThread()) {
+ Unlock();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheService::GetLockHeldTime(double *aLockHeldTime)
+{
+ MutexAutoLock lock(mTimeStampLock);
+
+ if (mLockAcquiredTimeStamp.IsNull()) {
+ *aLockHeldTime = 0.0;
+ }
+ else {
+ *aLockHeldTime =
+ (TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Internal Methods
+ */
+nsresult
+nsCacheService::CreateDiskDevice()
+{
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableDiskDevice) return NS_ERROR_NOT_AVAILABLE;
+ if (mDiskDevice) return NS_OK;
+
+ mDiskDevice = new nsDiskCacheDevice;
+ if (!mDiskDevice) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set the preferences
+ mDiskDevice->SetCacheParentDirectory(mObserver->DiskCacheParentDirectory());
+ mDiskDevice->SetCapacity(mObserver->DiskCacheCapacity());
+ mDiskDevice->SetMaxEntrySize(mObserver->DiskCacheMaxEntrySize());
+
+ nsresult rv = mDiskDevice->Init();
+ if (NS_FAILED(rv)) {
+#if DEBUG
+ printf("###\n");
+ printf("### mDiskDevice->Init() failed (0x%.8x)\n",
+ static_cast<uint32_t>(rv));
+ printf("### - disabling disk cache for this session.\n");
+ printf("###\n");
+#endif
+ mEnableDiskDevice = false;
+ delete mDiskDevice;
+ mDiskDevice = nullptr;
+ return rv;
+ }
+
+ NS_ASSERTION(!mSmartSizeTimer, "Smartsize timer was already fired!");
+
+ // Disk device is usually created during the startup. Delay smart size
+ // calculation to avoid possible massive IO caused by eviction of entries
+ // in case the new smart size is smaller than current cache usage.
+ mSmartSizeTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = mSmartSizeTimer->InitWithCallback(new nsSetDiskSmartSizeCallback(),
+ 1000*60*3,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to post smart size timer");
+ mSmartSizeTimer = nullptr;
+ }
+ } else {
+ NS_WARNING("Can't create smart size timer");
+ }
+ // Ignore state of the timer and return success since the purpose of the
+ // method (create the disk-device) has been fulfilled
+
+ return NS_OK;
+}
+
+// Runnable sent from cache thread to main thread
+class nsDisableOldMaxSmartSizePrefEvent: public Runnable
+{
+public:
+ nsDisableOldMaxSmartSizePrefEvent() {}
+
+ NS_IMETHOD Run() override
+ {
+ // Main thread may have already called nsCacheService::Shutdown
+ if (!nsCacheService::IsInitialized())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = branch->SetBoolPref(DISK_CACHE_USE_OLD_MAX_SMART_SIZE_PREF, false);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to disable old max smart size");
+ return rv;
+ }
+
+ // It is safe to call SetDiskSmartSize_Locked() without holding the lock
+ // when we are on main thread and nsCacheService is initialized.
+ nsCacheService::gService->SetDiskSmartSize_Locked();
+
+ if (nsCacheService::gService->mObserver->PermittedToSmartSize(branch, false)) {
+ rv = branch->SetIntPref(DISK_CACHE_CAPACITY_PREF, MAX_CACHE_SIZE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set cache capacity pref");
+ }
+ }
+
+ return NS_OK;
+ }
+};
+
+void
+nsCacheService::MarkStartingFresh()
+{
+ if (!gService || !gService->mObserver->ShouldUseOldMaxSmartSize()) {
+ // Already using new max, nothing to do here
+ return;
+ }
+
+ gService->mObserver->SetUseNewMaxSmartSize(true);
+
+ // We always dispatch an event here because we don't want to deal with lock
+ // reentrance issues.
+ NS_DispatchToMainThread(new nsDisableOldMaxSmartSizePrefEvent());
+}
+
+nsresult
+nsCacheService::GetOfflineDevice(nsOfflineCacheDevice **aDevice)
+{
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aDevice = mOfflineDevice);
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::GetCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice)
+{
+ nsresult rv;
+
+ nsAutoString profilePath;
+ rv = aProfileDir->GetPath(profilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
+ rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (*aDevice)->SetAutoShutdown();
+ mCustomOfflineDevices.Put(profilePath, *aDevice);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateOfflineDevice()
+{
+ CACHE_LOG_INFO(("Creating default offline device"));
+
+ if (mOfflineDevice) return NS_OK;
+ if (!nsCacheService::IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = CreateCustomOfflineDevice(
+ mObserver->OfflineCacheParentDirectory(),
+ mObserver->OfflineCacheCapacity(),
+ &mOfflineDevice);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateCustomOfflineDevice(nsIFile *aProfileDir,
+ int32_t aQuota,
+ nsOfflineCacheDevice **aDevice)
+{
+ NS_ENSURE_ARG(aProfileDir);
+
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
+ nsAutoCString profilePath;
+ aProfileDir->GetNativePath(profilePath);
+ CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
+ profilePath.BeginReading(), aQuota));
+ }
+
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
+
+ *aDevice = new nsOfflineCacheDevice;
+
+ NS_ADDREF(*aDevice);
+
+ // set the preferences
+ (*aDevice)->SetCacheParentDirectory(aProfileDir);
+ (*aDevice)->SetCapacity(aQuota);
+
+ nsresult rv = (*aDevice)->InitWithSqlite(mStorageService);
+ if (NS_FAILED(rv)) {
+ CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8x)\n", rv));
+ CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
+
+ NS_RELEASE(*aDevice);
+ }
+ return rv;
+}
+
+nsresult
+nsCacheService::CreateMemoryDevice()
+{
+ if (!mInitialized) return NS_ERROR_NOT_AVAILABLE;
+ if (!mEnableMemoryDevice) return NS_ERROR_NOT_AVAILABLE;
+ if (mMemoryDevice) return NS_OK;
+
+ mMemoryDevice = new nsMemoryCacheDevice;
+ if (!mMemoryDevice) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set preference
+ int32_t capacity = mObserver->MemoryCacheCapacity();
+ CACHE_LOG_DEBUG(("Creating memory device with capacity %d\n", capacity));
+ mMemoryDevice->SetCapacity(capacity);
+ mMemoryDevice->SetMaxEntrySize(mObserver->MemoryCacheMaxEntrySize());
+
+ nsresult rv = mMemoryDevice->Init();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Initialization of Memory Cache failed.");
+ delete mMemoryDevice;
+ mMemoryDevice = nullptr;
+ }
+
+ return rv;
+}
+
+nsresult
+nsCacheService::RemoveCustomOfflineDevice(nsOfflineCacheDevice *aDevice)
+{
+ nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
+ if (!profileDir)
+ return NS_ERROR_UNEXPECTED;
+
+ nsAutoString profilePath;
+ nsresult rv = profileDir->GetPath(profilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCustomOfflineDevices.Remove(profilePath);
+ return NS_OK;
+}
+
+nsresult
+nsCacheService::CreateRequest(nsCacheSession * session,
+ const nsACString & clientKey,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener * listener,
+ nsCacheRequest ** request)
+{
+ NS_ASSERTION(request, "CreateRequest: request is null");
+
+ nsAutoCString key(*session->ClientID());
+ key.Append(':');
+ key.Append(clientKey);
+
+ if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
+
+ // create request
+ *request = new nsCacheRequest(key, listener, accessRequested,
+ blockingMode, session);
+
+ if (!listener) return NS_OK; // we're sync, we're done.
+
+ // get the request's thread
+ (*request)->mThread = do_GetCurrentThread();
+
+ return NS_OK;
+}
+
+
+class nsCacheListenerEvent : public Runnable
+{
+public:
+ nsCacheListenerEvent(nsICacheListener *listener,
+ nsICacheEntryDescriptor *descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult status)
+ : mListener(listener) // transfers reference
+ , mDescriptor(descriptor) // transfers reference (may be null)
+ , mAccessGranted(accessGranted)
+ , mStatus(status)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
+
+ NS_RELEASE(mListener);
+ NS_IF_RELEASE(mDescriptor);
+ return NS_OK;
+ }
+
+private:
+ // We explicitly leak mListener or mDescriptor if Run is not called
+ // because otherwise we cannot guarantee that they are destroyed on
+ // the right thread.
+
+ nsICacheListener *mListener;
+ nsICacheEntryDescriptor *mDescriptor;
+ nsCacheAccessMode mAccessGranted;
+ nsresult mStatus;
+};
+
+
+nsresult
+nsCacheService::NotifyListener(nsCacheRequest * request,
+ nsICacheEntryDescriptor * descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult status)
+{
+ NS_ASSERTION(request->mThread, "no thread set in async request!");
+
+ // Swap ownership, and release listener on target thread...
+ nsICacheListener *listener = request->mListener;
+ request->mListener = nullptr;
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsCacheListenerEvent(listener, descriptor,
+ accessGranted, status);
+ if (!ev) {
+ // Better to leak listener and descriptor if we fail because we don't
+ // want to destroy them inside the cache service lock or on potentially
+ // the wrong thread.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return request->mThread->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+
+nsresult
+nsCacheService::ProcessRequest(nsCacheRequest * request,
+ bool calledFromOpenCacheEntry,
+ nsICacheEntryDescriptor ** result)
+{
+ // !!! must be called with mLock held !!!
+ nsresult rv;
+ nsCacheEntry * entry = nullptr;
+ nsCacheEntry * doomedEntry = nullptr;
+ nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
+ if (result) *result = nullptr;
+
+ while(1) { // Activate entry loop
+ rv = ActivateEntry(request, &entry, &doomedEntry); // get the entry for this request
+ if (NS_FAILED(rv)) break;
+
+ while(1) { // Request Access loop
+ NS_ASSERTION(entry, "no entry in Request Access loop!");
+ // entry->RequestAccess queues request on entry
+ rv = entry->RequestAccess(request, &accessGranted);
+ if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
+
+ if (request->IsBlocking()) {
+ if (request->mListener) {
+ // async exits - validate, doom, or close will resume
+ return rv;
+ }
+
+ // XXX this is probably wrong...
+ Unlock();
+ rv = request->WaitForValidation();
+ Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
+ }
+
+ PR_REMOVE_AND_INIT_LINK(request);
+ if (NS_FAILED(rv)) break; // non-blocking mode returns WAIT_FOR_VALIDATION error
+ // okay, we're ready to process this request, request access again
+ }
+ if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
+
+ if (entry->IsNotInUse()) {
+ // this request was the last one keeping it around, so get rid of it
+ DeactivateEntry(entry);
+ }
+ // loop back around to look for another entry
+ }
+
+ if (NS_SUCCEEDED(rv) && request->mProfileDir) {
+ // Custom cache directory has been demanded. Preset the cache device.
+ if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
+ // Failsafe check: this is implemented only for offline cache atm.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ RefPtr<nsOfflineCacheDevice> customCacheDevice;
+ rv = GetCustomOfflineDevice(request->mProfileDir, -1,
+ getter_AddRefs(customCacheDevice));
+ if (NS_SUCCEEDED(rv))
+ entry->SetCustomCacheDevice(customCacheDevice);
+ }
+ }
+
+ nsICacheEntryDescriptor *descriptor = nullptr;
+
+ if (NS_SUCCEEDED(rv))
+ rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
+
+ // If doomedEntry is set, ActivatEntry() doomed an existing entry and
+ // created a new one for that cache-key. However, any pending requests
+ // on the doomed entry were not processed and we need to do that here.
+ // This must be done after adding the created entry to list of active
+ // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
+ // (see bug ##561313). It is also important to do this after creating a
+ // descriptor for this request, or some other request may end up being
+ // executed first for the newly created entry.
+ // Finally, it is worth to emphasize that if doomedEntry is set,
+ // ActivateEntry() created a new entry for the request, which will be
+ // initialized by RequestAccess() and they both should have returned NS_OK.
+ if (doomedEntry) {
+ (void) ProcessPendingRequests(doomedEntry);
+ if (doomedEntry->IsNotInUse())
+ DeactivateEntry(doomedEntry);
+ doomedEntry = nullptr;
+ }
+
+ if (request->mListener) { // Asynchronous
+
+ if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
+ return rv; // skip notifying listener, just return rv to caller
+
+ // call listener to report error or descriptor
+ nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
+ rv = rv2; // trigger delete request
+ }
+ } else { // Synchronous
+ *result = descriptor;
+ }
+ return rv;
+}
+
+
+nsresult
+nsCacheService::OpenCacheEntry(nsCacheSession * session,
+ const nsACString & key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener * listener,
+ nsICacheEntryDescriptor ** result)
+{
+ CACHE_LOG_DEBUG(("Opening entry for session %p, key %s, mode %d, blocking %d\n",
+ session, PromiseFlatCString(key).get(), accessRequested,
+ blockingMode));
+ if (result)
+ *result = nullptr;
+
+ if (!gService || !gService->mInitialized)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCacheRequest * request = nullptr;
+
+ nsresult rv = gService->CreateRequest(session,
+ key,
+ accessRequested,
+ blockingMode,
+ listener,
+ &request);
+ if (NS_FAILED(rv)) return rv;
+
+ CACHE_LOG_DEBUG(("Created request %p\n", request));
+
+ // Process the request on the background thread if we are on the main thread
+ // and the the request is asynchronous
+ if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
+ nsCOMPtr<nsIRunnable> ev =
+ new nsProcessRequestEvent(request);
+ rv = DispatchToCacheIOThread(ev);
+
+ // delete request if we didn't post the event
+ if (NS_FAILED(rv))
+ delete request;
+ }
+ else {
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
+ rv = gService->ProcessRequest(request, true, result);
+
+ // delete requests that have completed
+ if (!(listener && blockingMode &&
+ (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
+ delete request;
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsCacheService::ActivateEntry(nsCacheRequest * request,
+ nsCacheEntry ** result,
+ nsCacheEntry ** doomedEntry)
+{
+ CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
+ if (!mInitialized || mClearingEntries)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
+ if (result) *result = nullptr;
+ if (doomedEntry) *doomedEntry = nullptr;
+ if ((!request) || (!result) || (!doomedEntry))
+ return NS_ERROR_NULL_POINTER;
+
+ // check if the request can be satisfied
+ if (!mEnableMemoryDevice && !request->IsStreamBased())
+ return NS_ERROR_FAILURE;
+ if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
+ return NS_ERROR_FAILURE;
+
+ // search active entries (including those not bound to device)
+ nsCacheEntry *entry = mActiveEntries.GetEntry(&(request->mKey));
+ CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
+
+ if (!entry) {
+ // search cache devices for entry
+ bool collision = false;
+ entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(), &collision);
+ CACHE_LOG_DEBUG(("Device search for request %p returned %p\n",
+ request, entry));
+ // When there is a hashkey collision just refuse to cache it...
+ if (collision) return NS_ERROR_CACHE_IN_USE;
+
+ if (entry) entry->MarkInitialized();
+ } else {
+ NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
+ }
+
+ if (entry) {
+ ++mCacheHits;
+ entry->Fetched();
+ } else {
+ ++mCacheMisses;
+ }
+
+ if (entry &&
+ ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
+ ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
+ (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
+ request->WillDoomEntriesIfExpired()))))
+
+ {
+ // this is FORCE-WRITE request or the entry has expired
+ // we doom entry without processing pending requests, but store it in
+ // doomedEntry which causes pending requests to be processed below
+ rv = DoomEntry_Internal(entry, false);
+ *doomedEntry = entry;
+ if (NS_FAILED(rv)) {
+ // XXX what to do? Increment FailedDooms counter?
+ }
+ entry = nullptr;
+ }
+
+ if (!entry) {
+ if (! (request->AccessRequested() & nsICache::ACCESS_WRITE)) {
+ // this is a READ-ONLY request
+ rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
+ goto error;
+ }
+
+ entry = new nsCacheEntry(request->mKey,
+ request->IsStreamBased(),
+ request->StoragePolicy());
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (request->IsPrivate())
+ entry->MarkPrivate();
+
+ entry->Fetched();
+ ++mTotalEntries;
+
+ // XXX we could perform an early bind in some cases based on storage policy
+ }
+
+ if (!entry->IsActive()) {
+ rv = mActiveEntries.AddEntry(entry);
+ if (NS_FAILED(rv)) goto error;
+ CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
+ entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
+ }
+ *result = entry;
+ return NS_OK;
+
+ error:
+ *result = nullptr;
+ delete entry;
+ return rv;
+}
+
+
+nsCacheEntry *
+nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, bool *collision)
+{
+ Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
+ nsCacheEntry * entry = nullptr;
+
+ CACHE_LOG_DEBUG(("mMemoryDevice: 0x%p\n", mMemoryDevice));
+
+ *collision = false;
+ if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
+ // If there is no memory device, then there is nothing to search...
+ if (mMemoryDevice) {
+ entry = mMemoryDevice->FindEntry(key, collision);
+ CACHE_LOG_DEBUG(("Searching mMemoryDevice for key %s found: 0x%p, "
+ "collision: %d\n", key->get(), entry, collision));
+ }
+ }
+
+ if (!entry &&
+ ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
+
+ if (mEnableDiskDevice) {
+ if (!mDiskDevice) {
+ nsresult rv = CreateDiskDevice();
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ entry = mDiskDevice->FindEntry(key, collision);
+ }
+ }
+
+ if (!entry && (policy == nsICache::STORE_OFFLINE ||
+ (policy == nsICache::STORE_ANYWHERE &&
+ gIOService->IsOffline()))) {
+
+ if (mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ if (NS_FAILED(rv))
+ return nullptr;
+ }
+
+ entry = mOfflineDevice->FindEntry(key, collision);
+ }
+ }
+
+ return entry;
+}
+
+
+nsCacheDevice *
+nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
+{
+ nsCacheDevice * device = entry->CacheDevice();
+ // return device if found, possibly null if the entry is doomed i.e prevent
+ // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
+ if (device || entry->IsDoomed()) return device;
+
+ int64_t predictedDataSize = entry->PredictedDataSize();
+ if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
+ // this is the default
+ if (!mDiskDevice) {
+ (void)CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
+ }
+
+ if (mDiskDevice) {
+ // Bypass the cache if Content-Length says the entry will be too big
+ if (predictedDataSize != -1 &&
+ mDiskDevice->EntryIsTooBig(predictedDataSize)) {
+ DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return nullptr;
+ }
+
+ entry->MarkBinding(); // enter state of binding
+ nsresult rv = mDiskDevice->BindEntry(entry);
+ entry->ClearBinding(); // exit state of binding
+ if (NS_SUCCEEDED(rv))
+ device = mDiskDevice;
+ }
+ }
+
+ // if we can't use mDiskDevice, try mMemoryDevice
+ if (!device && mEnableMemoryDevice && entry->IsAllowedInMemory()) {
+ if (!mMemoryDevice) {
+ (void)CreateMemoryDevice(); // ignore the error (check for mMemoryDevice instead)
+ }
+ if (mMemoryDevice) {
+ // Bypass the cache if Content-Length says entry will be too big
+ if (predictedDataSize != -1 &&
+ mMemoryDevice->EntryIsTooBig(predictedDataSize)) {
+ DebugOnly<nsresult> rv = nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"DoomEntry() failed.");
+ return nullptr;
+ }
+
+ entry->MarkBinding(); // enter state of binding
+ nsresult rv = mMemoryDevice->BindEntry(entry);
+ entry->ClearBinding(); // exit state of binding
+ if (NS_SUCCEEDED(rv))
+ device = mMemoryDevice;
+ }
+ }
+
+ if (!device && entry->IsStreamData() &&
+ entry->IsAllowedOffline() && mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead)
+ }
+
+ device = entry->CustomCacheDevice()
+ ? entry->CustomCacheDevice()
+ : mOfflineDevice;
+
+ if (device) {
+ entry->MarkBinding();
+ nsresult rv = device->BindEntry(entry);
+ entry->ClearBinding();
+ if (NS_FAILED(rv))
+ device = nullptr;
+ }
+ }
+
+ if (device)
+ entry->SetCacheDevice(device);
+ return device;
+}
+
+nsresult
+nsCacheService::DoomEntry(nsCacheEntry * entry)
+{
+ return gService->DoomEntry_Internal(entry, true);
+}
+
+
+nsresult
+nsCacheService::DoomEntry_Internal(nsCacheEntry * entry,
+ bool doProcessPendingRequests)
+{
+ if (entry->IsDoomed()) return NS_OK;
+
+ CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
+ nsresult rv = NS_OK;
+ entry->MarkDoomed();
+
+ NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
+ nsCacheDevice * device = entry->CacheDevice();
+ if (device) device->DoomEntry(entry);
+
+ if (entry->IsActive()) {
+ // remove from active entries
+ mActiveEntries.RemoveEntry(entry);
+ CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
+ entry->MarkInactive();
+ }
+
+ // put on doom list to wait for descriptors to close
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
+ PR_APPEND_LINK(entry, &mDoomedEntries);
+
+ // handle pending requests only if we're supposed to
+ if (doProcessPendingRequests) {
+ // tell pending requests to get on with their lives...
+ rv = ProcessPendingRequests(entry);
+
+ // All requests have been removed, but there may still be open descriptors
+ if (entry->IsNotInUse()) {
+ DeactivateEntry(entry); // tell device to get rid of it
+ }
+ }
+ return rv;
+}
+
+
+void
+nsCacheService::OnProfileShutdown()
+{
+ if (!gService || !gService->mInitialized) {
+ // The cache service has been shut down, but someone is still holding
+ // a reference to it. Ignore this call.
+ return;
+ }
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+ gService->mClearingEntries = true;
+ gService->DoomActiveEntries(nullptr);
+ }
+
+ gService->CloseAllStreams();
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+ gService->ClearDoomList();
+
+ // Make sure to wait for any pending cache-operations before
+ // proceeding with destructive actions (bug #620660)
+ (void) SyncWithCacheIOThread();
+
+ if (gService->mDiskDevice && gService->mEnableDiskDevice) {
+ gService->mDiskDevice->Shutdown();
+ }
+ gService->mEnableDiskDevice = false;
+
+ if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
+ gService->mOfflineDevice->Shutdown();
+ }
+ for (auto iter = gService->mCustomOfflineDevices.Iter();
+ !iter.Done(); iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+
+ gService->mEnableOfflineDevice = false;
+
+ if (gService->mMemoryDevice) {
+ // clear memory cache
+ gService->mMemoryDevice->EvictEntries(nullptr);
+ }
+
+ gService->mClearingEntries = false;
+}
+
+
+void
+nsCacheService::OnProfileChanged()
+{
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
+
+ gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+ gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory());
+ gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
+
+ // XXX initialization of mDiskDevice could be made lazily, if mEnableDiskDevice is false
+ nsresult rv = gService->mDiskDevice->Init();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing disk device failed");
+ gService->mEnableDiskDevice = false;
+ // XXX delete mDiskDevice?
+ }
+ }
+
+ if (gService->mOfflineDevice) {
+ gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory());
+ gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity());
+
+ // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false
+ nsresult rv = gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed");
+ gService->mEnableOfflineDevice = false;
+ // XXX delete mOfflineDevice?
+ }
+ }
+
+ // If memoryDevice exists, reset its size to the new profile
+ if (gService->mMemoryDevice) {
+ if (gService->mEnableMemoryDevice) {
+ // make sure that capacity is reset to the right value
+ int32_t capacity = gService->mObserver->MemoryCacheCapacity();
+ CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
+ capacity));
+ gService->mMemoryDevice->SetCapacity(capacity);
+ } else {
+ // tell memory device to evict everything
+ CACHE_LOG_DEBUG(("memory device disabled\n"));
+ gService->mMemoryDevice->SetCapacity(0);
+ // Don't delete memory device, because some entries may be active still...
+ }
+ }
+}
+
+
+void
+nsCacheService::SetDiskCacheEnabled(bool enabled)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEENABLED));
+ gService->mEnableDiskDevice = enabled;
+}
+
+
+void
+nsCacheService::SetDiskCacheCapacity(int32_t capacity)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHECAPACITY));
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetCapacity(capacity);
+ }
+
+ gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
+}
+
+void
+nsCacheService::SetDiskCacheMaxEntrySize(int32_t maxSize)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKCACHEMAXENTRYSIZE));
+
+ if (gService->mDiskDevice) {
+ gService->mDiskDevice->SetMaxEntrySize(maxSize);
+ }
+}
+
+void
+nsCacheService::SetMemoryCacheMaxEntrySize(int32_t maxSize)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHEMAXENTRYSIZE));
+
+ if (gService->mMemoryDevice) {
+ gService->mMemoryDevice->SetMaxEntrySize(maxSize);
+ }
+}
+
+void
+nsCacheService::SetOfflineCacheEnabled(bool enabled)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
+ gService->mEnableOfflineDevice = enabled;
+}
+
+void
+nsCacheService::SetOfflineCacheCapacity(int32_t capacity)
+{
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
+
+ if (gService->mOfflineDevice) {
+ gService->mOfflineDevice->SetCapacity(capacity);
+ }
+
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+}
+
+
+void
+nsCacheService::SetMemoryCache()
+{
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::SetMemoryCache"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETMEMORYCACHE));
+
+ gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
+
+ if (gService->mEnableMemoryDevice) {
+ if (gService->mMemoryDevice) {
+ int32_t capacity = gService->mObserver->MemoryCacheCapacity();
+ // make sure that capacity is reset to the right value
+ CACHE_LOG_DEBUG(("Resetting memory device capacity to %d\n",
+ capacity));
+ gService->mMemoryDevice->SetCapacity(capacity);
+ }
+ } else {
+ if (gService->mMemoryDevice) {
+ // tell memory device to evict everything
+ CACHE_LOG_DEBUG(("memory device disabled\n"));
+ gService->mMemoryDevice->SetCapacity(0);
+ // Don't delete memory device, because some entries may be active still...
+ }
+ }
+}
+
+
+/******************************************************************************
+ * static methods for nsCacheEntryDescriptor
+ *****************************************************************************/
+void
+nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
+{
+ // ask entry to remove descriptor
+ nsCacheEntry * entry = descriptor->CacheEntry();
+ bool doomEntry;
+ bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
+
+ if (!entry->IsValid()) {
+ gService->ProcessPendingRequests(entry);
+ }
+
+ if (doomEntry) {
+ gService->DoomEntry_Internal(entry, true);
+ return;
+ }
+
+ if (!stillActive) {
+ gService->DeactivateEntry(entry);
+ }
+}
+
+
+nsresult
+nsCacheService::GetFileForEntry(nsCacheEntry * entry,
+ nsIFile ** result)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->GetFileForEntry(entry, result);
+}
+
+
+nsresult
+nsCacheService::OpenInputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream ** result)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OpenInputStreamForEntry(entry, mode, offset, result);
+}
+
+nsresult
+nsCacheService::OpenOutputStreamForEntry(nsCacheEntry * entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream ** result)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OpenOutputStreamForEntry(entry, mode, offset, result);
+}
+
+
+nsresult
+nsCacheService::OnDataSizeChange(nsCacheEntry * entry, int32_t deltaSize)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OnDataSizeChange(entry, deltaSize);
+}
+
+void
+nsCacheService::LockAcquired()
+{
+ MutexAutoLock lock(mTimeStampLock);
+ mLockAcquiredTimeStamp = TimeStamp::Now();
+}
+
+void
+nsCacheService::LockReleased()
+{
+ MutexAutoLock lock(mTimeStampLock);
+ mLockAcquiredTimeStamp = TimeStamp();
+}
+
+void
+nsCacheService::Lock()
+{
+ gService->mLock.Lock();
+ gService->LockAcquired();
+}
+
+void
+nsCacheService::Lock(mozilla::Telemetry::ID mainThreadLockerID)
+{
+ mozilla::Telemetry::ID lockerID;
+ mozilla::Telemetry::ID generalID;
+
+ if (NS_IsMainThread()) {
+ lockerID = mainThreadLockerID;
+ generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
+ } else {
+ lockerID = mozilla::Telemetry::HistogramCount;
+ generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
+ }
+
+ TimeStamp start(TimeStamp::Now());
+
+ nsCacheService::Lock();
+
+ TimeStamp stop(TimeStamp::Now());
+
+ // Telemetry isn't thread safe on its own, but this is OK because we're
+ // protecting it with the cache lock.
+ if (lockerID != mozilla::Telemetry::HistogramCount) {
+ mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
+ }
+ mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
+}
+
+void
+nsCacheService::Unlock()
+{
+ gService->mLock.AssertCurrentThreadOwns();
+
+ nsTArray<nsISupports*> doomed;
+ doomed.SwapElements(gService->mDoomedObjects);
+
+ gService->LockReleased();
+ gService->mLock.Unlock();
+
+ for (uint32_t i = 0; i < doomed.Length(); ++i)
+ doomed[i]->Release();
+}
+
+void
+nsCacheService::ReleaseObject_Locked(nsISupports * obj,
+ nsIEventTarget * target)
+{
+ gService->mLock.AssertCurrentThreadOwns();
+
+ bool isCur;
+ if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
+ gService->mDoomedObjects.AppendElement(obj);
+ } else {
+ NS_ProxyRelease(target, dont_AddRef(obj));
+ }
+}
+
+
+nsresult
+nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
+{
+ entry->SetData(element);
+ entry->TouchData();
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheService::ValidateEntry(nsCacheEntry * entry)
+{
+ nsCacheDevice * device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ entry->MarkValid();
+ nsresult rv = gService->ProcessPendingRequests(entry);
+ NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
+ // XXX what else should be done?
+
+ return rv;
+}
+
+
+int32_t
+nsCacheService::CacheCompressionLevel()
+{
+ int32_t level = gService->mObserver->CacheCompressionLevel();
+ return level;
+}
+
+
+void
+nsCacheService::DeactivateEntry(nsCacheEntry * entry)
+{
+ CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
+ nsresult rv = NS_OK;
+ NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
+ nsCacheDevice * device = nullptr;
+
+ if (mMaxDataSize < entry->DataSize() ) mMaxDataSize = entry->DataSize();
+ if (mMaxMetaSize < entry->MetaDataSize() ) mMaxMetaSize = entry->MetaDataSize();
+
+ if (entry->IsDoomed()) {
+ // remove from Doomed list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ } else if (entry->IsActive()) {
+ // remove from active entries
+ mActiveEntries.RemoveEntry(entry);
+ CACHE_LOG_DEBUG(("Removed deactivated entry %p from mActiveEntries\n",
+ entry));
+ entry->MarkInactive();
+
+ // bind entry if necessary to store meta-data
+ device = EnsureEntryHasDevice(entry);
+ if (!device) {
+ CACHE_LOG_DEBUG(("DeactivateEntry: unable to bind active "
+ "entry %p\n",
+ entry));
+ NS_WARNING("DeactivateEntry: unable to bind active entry\n");
+ return;
+ }
+ } else {
+ // if mInitialized == false,
+ // then we're shutting down and this state is okay.
+ NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
+ }
+
+ device = entry->CacheDevice();
+ if (device) {
+ rv = device->DeactivateEntry(entry);
+ if (NS_FAILED(rv)) {
+ // increment deactivate failure count
+ ++mDeactivateFailures;
+ }
+ } else {
+ // increment deactivating unbound entry statistic
+ ++mDeactivatedUnboundEntries;
+ delete entry; // because no one else will
+ }
+}
+
+
+nsresult
+nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
+{
+ nsresult rv = NS_OK;
+ nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
+ nsCacheRequest * nextRequest;
+ bool newWriter = false;
+
+ CACHE_LOG_DEBUG(("ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
+ (entry->IsInitialized()?"" : "Un"),
+ (entry->IsDoomed()?"DOOMED" : ""),
+ (entry->IsValid()? "V":"Inv"), entry));
+
+ if (request == &entry->mRequestQ) return NS_OK; // no queued requests
+
+ if (!entry->IsDoomed() && entry->IsInvalid()) {
+ // 1st descriptor closed w/o MarkValid()
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ), "shouldn't be here with open descriptors");
+
+#if DEBUG
+ // verify no ACCESS_WRITE requests(shouldn't have any of these)
+ while (request != &entry->mRequestQ) {
+ NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
+ "ACCESS_WRITE request should have been given a new entry");
+ request = (nsCacheRequest *)PR_NEXT_LINK(request);
+ }
+ request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
+#endif
+ // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st writer
+ while (request != &entry->mRequestQ) {
+ if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
+ newWriter = true;
+ CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
+ break;
+ }
+
+ request = (nsCacheRequest *)PR_NEXT_LINK(request);
+ }
+
+ if (request == &entry->mRequestQ) // no requests asked for ACCESS_READ_WRITE, back to top
+ request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
+
+ // XXX what should we do if there are only READ requests in queue?
+ // XXX serialize their accesses, give them only read access, but force them to check validate flag?
+ // XXX or do readers simply presume the entry is valid
+ // See fix for bug #467392 below
+ }
+
+ nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
+
+ while (request != &entry->mRequestQ) {
+ nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
+ CACHE_LOG_DEBUG((" %sync request %p for %p\n",
+ (request->mListener?"As":"S"), request, entry));
+
+ if (request->mListener) {
+
+ // Async request
+ PR_REMOVE_AND_INIT_LINK(request);
+
+ if (entry->IsDoomed()) {
+ rv = ProcessRequest(request, false, nullptr);
+ if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
+ rv = NS_OK;
+ else
+ delete request;
+
+ if (NS_FAILED(rv)) {
+ // XXX what to do?
+ }
+ } else if (entry->IsValid() || newWriter) {
+ rv = entry->RequestAccess(request, &accessGranted);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "if entry is valid, RequestAccess must succeed.");
+ // XXX if (newWriter) NS_ASSERTION( accessGranted == request->AccessRequested(), "why not?");
+
+ // entry->CreateDescriptor dequeues request, and queues descriptor
+ nsICacheEntryDescriptor *descriptor = nullptr;
+ rv = entry->CreateDescriptor(request,
+ accessGranted,
+ &descriptor);
+
+ // post call to listener to report error or descriptor
+ rv = NotifyListener(request, descriptor, accessGranted, rv);
+ delete request;
+ if (NS_FAILED(rv)) {
+ // XXX what to do?
+ }
+
+ } else {
+ // read-only request to an invalid entry - need to wait for
+ // the entry to become valid so we post an event to process
+ // the request again later (bug #467392)
+ nsCOMPtr<nsIRunnable> ev =
+ new nsProcessRequestEvent(request);
+ rv = DispatchToCacheIOThread(ev);
+ if (NS_FAILED(rv)) {
+ delete request; // avoid leak
+ }
+ }
+ } else {
+
+ // Synchronous request
+ request->WakeUp();
+ }
+ if (newWriter) break; // process remaining requests after validation
+ request = nextRequest;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsCacheService::IsDoomListEmpty()
+{
+ nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+ return &mDoomedEntries == entry;
+}
+
+void
+nsCacheService::ClearDoomList()
+{
+ nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+
+ while (entry != &mDoomedEntries) {
+ nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+
+ entry->DetachDescriptors();
+ DeactivateEntry(entry);
+ entry = next;
+ }
+}
+
+void
+nsCacheService::DoomActiveEntries(DoomCheckFn check)
+{
+ AutoTArray<nsCacheEntry*, 8> array;
+
+ for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
+ nsCacheEntry* entry =
+ static_cast<nsCacheEntryHashTableEntry*>(iter.Get())->cacheEntry;
+
+ if (check && !check(entry)) {
+ continue;
+ }
+
+ array.AppendElement(entry);
+
+ // entry is being removed from the active entry list
+ entry->MarkInactive();
+ iter.Remove();
+ }
+
+ uint32_t count = array.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ DoomEntry_Internal(array[i], true);
+ }
+}
+
+void
+nsCacheService::CloseAllStreams()
+{
+ nsTArray<RefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
+ nsTArray<RefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
+
+ nsTArray<nsCacheEntry*> entries;
+
+#if DEBUG
+ // make sure there is no active entry
+ for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<nsCacheEntryHashTableEntry*>(iter.Get());
+ entries.AppendElement(entry->cacheEntry);
+ }
+ NS_ASSERTION(entries.IsEmpty(), "Bad state");
+#endif
+
+ // Get doomed entries
+ nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
+ while (entry != &mDoomedEntries) {
+ nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
+ entries.AppendElement(entry);
+ entry = next;
+ }
+
+ // Iterate through all entries and collect input and output streams
+ for (size_t i = 0; i < entries.Length(); i++) {
+ entry = entries.ElementAt(i);
+
+ nsTArray<RefPtr<nsCacheEntryDescriptor> > descs;
+ entry->GetDescriptors(descs);
+
+ for (uint32_t j = 0 ; j < descs.Length() ; j++) {
+ if (descs[j]->mOutputWrapper)
+ outputs.AppendElement(descs[j]->mOutputWrapper);
+
+ for (size_t k = 0; k < descs[j]->mInputWrappers.Length(); k++)
+ inputs.AppendElement(descs[j]->mInputWrappers[k]);
+ }
+ }
+ }
+
+ uint32_t i;
+ for (i = 0 ; i < inputs.Length() ; i++)
+ inputs[i]->Close();
+
+ for (i = 0 ; i < outputs.Length() ; i++)
+ outputs[i]->Close();
+}
+
+
+bool
+nsCacheService::GetClearingEntries()
+{
+ AssertOwnsLock();
+ return gService->mClearingEntries;
+}
+
+// static
+void nsCacheService::GetCacheBaseDirectoty(nsIFile ** result)
+{
+ *result = nullptr;
+ if (!gService || !gService->mObserver)
+ return;
+
+ nsCOMPtr<nsIFile> directory =
+ gService->mObserver->DiskCacheParentDirectory();
+ if (!directory)
+ return;
+
+ directory->Clone(result);
+}
+
+// static
+void nsCacheService::GetDiskCacheDirectory(nsIFile ** result)
+{
+ nsCOMPtr<nsIFile> directory;
+ GetCacheBaseDirectoty(getter_AddRefs(directory));
+ if (!directory)
+ return;
+
+ nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("Cache"));
+ if (NS_FAILED(rv))
+ return;
+
+ directory.forget(result);
+}
+
+// static
+void nsCacheService::GetAppCacheDirectory(nsIFile ** result)
+{
+ nsCOMPtr<nsIFile> directory;
+ GetCacheBaseDirectoty(getter_AddRefs(directory));
+ if (!directory)
+ return;
+
+ nsresult rv = directory->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
+ if (NS_FAILED(rv))
+ return;
+
+ directory.forget(result);
+}
+
+
+void
+nsCacheService::LogCacheStatistics()
+{
+ uint32_t hitPercentage = (uint32_t)((((double)mCacheHits) /
+ ((double)(mCacheHits + mCacheMisses))) * 100);
+ CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
+ CACHE_LOG_INFO((" TotalEntries = %d\n", mTotalEntries));
+ CACHE_LOG_INFO((" Cache Hits = %d\n", mCacheHits));
+ CACHE_LOG_INFO((" Cache Misses = %d\n", mCacheMisses));
+ CACHE_LOG_INFO((" Cache Hit %% = %d%%\n", hitPercentage));
+ CACHE_LOG_INFO((" Max Key Length = %d\n", mMaxKeyLength));
+ CACHE_LOG_INFO((" Max Meta Size = %d\n", mMaxMetaSize));
+ CACHE_LOG_INFO((" Max Data Size = %d\n", mMaxDataSize));
+ CACHE_LOG_INFO(("\n"));
+ CACHE_LOG_INFO((" Deactivate Failures = %d\n",
+ mDeactivateFailures));
+ CACHE_LOG_INFO((" Deactivated Unbound Entries = %d\n",
+ mDeactivatedUnboundEntries));
+}
+
+nsresult
+nsCacheService::SetDiskSmartSize()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SETDISKSMARTSIZE));
+
+ if (!gService) return NS_ERROR_NOT_AVAILABLE;
+
+ return gService->SetDiskSmartSize_Locked();
+}
+
+nsresult
+nsCacheService::SetDiskSmartSize_Locked()
+{
+ nsresult rv;
+
+ if (mozilla::net::CacheObserver::UseNewCache()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mObserver->DiskCacheParentDirectory())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mDiskDevice)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mObserver->SmartSizeEnabled())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoString cachePath;
+ rv = mObserver->DiskCacheParentDirectory()->GetPath(cachePath);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRunnable> event =
+ new nsGetSmartSizeEvent(cachePath, mDiskDevice->getCacheSize(),
+ mObserver->ShouldUseOldMaxSmartSize());
+ DispatchToCacheIOThread(event);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+nsCacheService::MoveOrRemoveDiskCache(nsIFile *aOldCacheDir,
+ nsIFile *aNewCacheDir,
+ const char *aCacheSubdir)
+{
+ bool same;
+ if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same)
+ return;
+
+ nsCOMPtr<nsIFile> aOldCacheSubdir;
+ aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
+
+ nsresult rv = aOldCacheSubdir->AppendNative(
+ nsDependentCString(aCacheSubdir));
+ if (NS_FAILED(rv))
+ return;
+
+ bool exists;
+ if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists)
+ return;
+
+ nsCOMPtr<nsIFile> aNewCacheSubdir;
+ aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
+
+ rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
+ if (NS_FAILED(rv))
+ return;
+
+ nsAutoCString newPath;
+ rv = aNewCacheSubdir->GetNativePath(newPath);
+ if (NS_FAILED(rv))
+ return;
+
+ if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
+ // New cache directory does not exist, try to move the old one here
+ // rename needs an empty target directory
+
+ // Make sure the parent of the target sub-dir exists
+ rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
+ nsAutoCString oldPath;
+ rv = aOldCacheSubdir->GetNativePath(oldPath);
+ if (NS_FAILED(rv))
+ return;
+ if (rename(oldPath.get(), newPath.get()) == 0)
+ return;
+ }
+ }
+
+ // Delay delete by 1 minute to avoid IO thrash on startup.
+ nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
+}
+
+static bool
+IsEntryPrivate(nsCacheEntry* entry)
+{
+ return entry->IsPrivate();
+}
+
+void
+nsCacheService::LeavePrivateBrowsing()
+{
+ nsCacheServiceAutoLock lock;
+
+ gService->DoomActiveEntries(IsEntryPrivate);
+
+ if (gService->mMemoryDevice) {
+ // clear memory cache
+ gService->mMemoryDevice->EvictPrivateEntries();
+ }
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(DiskCacheDeviceMallocSizeOf)
+
+NS_IMETHODIMP
+nsCacheService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ size_t disk = 0;
+ if (mDiskDevice) {
+ nsCacheServiceAutoLock
+ lock(LOCK_TELEM(NSCACHESERVICE_DISKDEVICEHEAPSIZE));
+ disk = mDiskDevice->SizeOfIncludingThis(DiskCacheDeviceMallocSizeOf);
+ }
+
+ size_t memory = mMemoryDevice ? mMemoryDevice->TotalSize() : 0;
+
+ MOZ_COLLECT_REPORT(
+ "explicit/network/disk-cache", KIND_HEAP, UNITS_BYTES, disk,
+ "Memory used by the network disk cache.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/network/memory-cache", KIND_HEAP, UNITS_BYTES, memory,
+ "Memory used by the network memory cache.");
+
+ return NS_OK;
+}