gecko-dev/netwerk/cache/nsCacheService.cpp
Ehsan Akhgari a903aefdc0 Backed out 9 changesets (bug 943660, bug 936964) because of ASAN use-after-free crashes on browser-chrome and mochitest-other
Backed out changeset 85486c4aa3d8 (bug 936964)
Backed out changeset 25312eb71998 (bug 936964)
Backed out changeset 6dbb8333960c (bug 936964)
Backed out changeset da6465ad476f (bug 936964)
Backed out changeset a87ffc992f38 (bug 936964)
Backed out changeset 4ae3a61182db (bug 936964)
Backed out changeset 34e9c3137804 (bug 936964)
Backed out changeset fd1459e71585 (bug 936964)
Backed out changeset 3e8a701d8bdc (bug 943660)

Landed on a CLOSED TREE

--HG--
rename : content/canvas/src/WebGLMemoryTracker.h => content/canvas/src/WebGLMemoryReporterWrapper.h
2013-11-27 20:05:00 -05:00

3180 lines
104 KiB
C++

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