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