mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-14 14:02:47 +00:00
a903aefdc0
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
3180 lines
104 KiB
C++
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();
|
|
}
|
|
}
|