pjs/netwerk/cache/mgr/nsCacheManager.cpp

648 строки
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998-1999 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Scott Furman, fur@netscape.com
*/
#include "nsINetDataCache.h"
#include "nsCacheManager.h"
#include "nsCachedNetData.h"
#include "nsReplacementPolicy.h"
#include "nsString.h"
#include "nsIURI.h"
#include "nsHashtable.h"
#include "nsIComponentManager.h"
#include "nsINetDataDiskCache.h"
#include "nsIPref.h"
#include "nsIServiceManager.h"
#include "nsIObserverService.h"
#define FILE_CACHE_IS_READY
// Limit the number of entries in the cache to conserve memory space
// in the nsReplacementPolicy code.
// MAX_DISK_CACHE_ENTRIES should be one more than MAX_DISK_CACHE_RECORDS
// defined in nsNetDiskCache.cpp
#define MAX_MEM_CACHE_ENTRIES 800
#define MAX_DISK_CACHE_ENTRIES 513
// Cache capacities in MB, overridable via APIs
#define DEFAULT_MEMORY_CACHE_CAPACITY 1024
#define DEFAULT_DISK_CACHE_CAPACITY 10000
const char* CACHE_MEM_CAPACITY = "browser.cache.memory_cache_size";
const char* CACHE_DISK_CAPACITY = "browser.cache.disk_cache_size";
static int PR_CALLBACK diskCacheSizeChanged(const char *pref, void *closure)
{
nsresult rv;
NS_WITH_SERVICE(nsIPref, prefs, NS_PREF_CONTRACTID, &rv);
PRInt32 capacity = DEFAULT_DISK_CACHE_CAPACITY;
if ( NS_SUCCEEDED (rv ) )
{
rv = prefs->GetIntPref(CACHE_DISK_CAPACITY, &capacity );
}
return ( (nsCacheManager*)closure )->SetDiskCacheCapacity( capacity );
}
static int PR_CALLBACK memCacheSizeChanged(const char *pref, void *closure)
{
nsresult rv;
PRInt32 capacity = DEFAULT_MEMORY_CACHE_CAPACITY ;
NS_WITH_SERVICE(nsIPref, prefs, NS_PREF_CONTRACTID, &rv);
if ( NS_SUCCEEDED (rv ) )
{
rv = prefs->GetIntPref(CACHE_MEM_CAPACITY, &capacity );
}
return ( (nsCacheManager*)closure )->SetMemCacheCapacity( capacity );
}
#define CACHE_HIGH_WATER_MARK(capacity) ((PRUint32)(0.98 * (capacity)))
#define CACHE_LOW_WATER_MARK(capacity) ((PRUint32)(0.75 * (capacity)))
nsCacheManager* gCacheManager = 0;
PRBool gCacheManagerNeedToEvict = PR_FALSE;
NS_IMPL_ISUPPORTS2(nsCacheManager, nsINetDataCacheManager, nsIObserver)
nsCacheManager::nsCacheManager()
: mActiveCacheRecords(0),
mDiskCacheCapacity(DEFAULT_DISK_CACHE_CAPACITY),
mMemCacheCapacity(DEFAULT_MEMORY_CACHE_CAPACITY)
{
NS_ASSERTION(!gCacheManager, "Multiple cache managers created");
gCacheManager = this;
gCacheManagerNeedToEvict = PR_FALSE;
NS_INIT_REFCNT();
}
nsCacheManager::~nsCacheManager()
{
gCacheManager = 0;
delete mActiveCacheRecords;
delete mMemSpaceManager;
delete mDiskSpaceManager;
nsresult rv;
NS_WITH_SERVICE(nsIPref, prefs, NS_PREF_CONTRACTID, &rv);
if ( NS_SUCCEEDED (rv ) )
{
prefs->UnregisterCallback( CACHE_DISK_CAPACITY, diskCacheSizeChanged, this);
prefs->UnregisterCallback( CACHE_MEM_CAPACITY, memCacheSizeChanged, this);
}
// Unregister this memory pressure observer
NS_WITH_SERVICE(nsIObserverService, os, NS_OBSERVERSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
rv = os->RemoveObserver(this, NS_MEMORY_PRESSURE_TOPIC);
}
}
nsresult nsCacheManager::InitPrefs()
{
nsresult rv;
NS_WITH_SERVICE(nsIPref, prefs, NS_PREF_CONTRACTID, &rv);
if ( NS_FAILED (rv ) )
return rv;
rv = prefs->RegisterCallback( CACHE_DISK_CAPACITY, diskCacheSizeChanged, this);
if ( NS_FAILED( rv ) )
return rv;
rv = prefs->RegisterCallback( CACHE_MEM_CAPACITY, memCacheSizeChanged, this);
if ( NS_FAILED( rv ) )
return rv;
// Init the prefs
diskCacheSizeChanged( CACHE_DISK_CAPACITY, this );
memCacheSizeChanged( CACHE_MEM_CAPACITY, this );
return rv;
}
nsresult
nsCacheManager::Init()
{
nsresult rv;
mActiveCacheRecords = new nsHashtable(64);
if (!mActiveCacheRecords)
return NS_ERROR_OUT_OF_MEMORY;
// Register as a memory pressure observer
NS_WITH_SERVICE(nsIObserverService, os, NS_OBSERVERSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
rv = os->AddObserver(this, NS_MEMORY_PRESSURE_TOPIC);
}
// Ignore any failure of the memory pressure registration...
// Instantiate the memory cache component
rv = nsComponentManager::CreateInstance(NS_NETWORK_MEMORY_CACHE_CONTRACTID,
nsnull,
NS_GET_IID(nsINetDataCache),
getter_AddRefs(mMemCache));
if (NS_FAILED(rv))
return rv;
rv = nsComponentManager::CreateInstance(NS_NETWORK_FLAT_CACHE_CONTRACTID,
nsnull,
NS_GET_IID(nsINetDataCache),
getter_AddRefs(mFlatCache));
if (NS_FAILED(rv)) {
// For now, we don't require a flat cache module to be present
if (rv != NS_ERROR_FACTORY_NOT_REGISTERED)
return rv;
}
#ifdef FILE_CACHE_IS_READY
// Instantiate the file cache component
rv = nsComponentManager::CreateInstance(NS_NETWORK_FILE_CACHE_CONTRACTID,
nsnull,
NS_GET_IID(nsINetDataCache),
getter_AddRefs(mDiskCache));
if (NS_FAILED(rv)) {
NS_WARNING("No disk cache present");
}
#endif
// Set up linked list of caches in search order
mCacheSearchChain = mMemCache;
if (mFlatCache) {
mMemCache->SetNextCache(mFlatCache);
mFlatCache->SetNextCache(mDiskCache);
} else {
mMemCache->SetNextCache(mDiskCache);
}
// TODO - Load any extension caches here
// Initialize replacement policy for memory cache module
mMemSpaceManager = new nsReplacementPolicy;
if (!mMemSpaceManager)
return NS_ERROR_OUT_OF_MEMORY;
rv = mMemSpaceManager->Init(MAX_MEM_CACHE_ENTRIES);
if (NS_FAILED(rv)) return rv;
rv = mMemSpaceManager->AddCache(mMemCache);
// Initialize replacement policy for disk cache modules (file
// cache and flat cache)
mDiskSpaceManager = new nsReplacementPolicy;
if (!mDiskSpaceManager)
return NS_ERROR_OUT_OF_MEMORY;
rv = mDiskSpaceManager->Init(MAX_DISK_CACHE_ENTRIES);
if (NS_FAILED(rv)) return rv;
if (mDiskCache) {
rv = mDiskSpaceManager->AddCache(mDiskCache);
if (NS_FAILED(rv)) return rv;
}
if (mFlatCache) {
rv = mDiskSpaceManager->AddCache(mFlatCache);
if (NS_FAILED(rv)) return rv;
}
InitPrefs();
return NS_OK;
}
nsresult nsCacheManager::GetCacheAndReplacementPolicy( PRUint32 aFlags, nsINetDataCache*& cache, nsReplacementPolicy *&spaceManager )
{
PRBool diskCacheEnabled = PR_FALSE;
PRBool memCacheEnabled = PR_FALSE;
// Initialize the out parameters...
cache = nsnull;
spaceManager = nsnull;
// Determine if the Disk cache is available...
if ( mDiskCache.get() ) {
mDiskCache->GetEnabled( &diskCacheEnabled );
// Ensure that the cache is initialized
if ((mDiskCacheCapacity == (PRUint32)-1) || (mDiskCacheCapacity == 0))
diskCacheEnabled = PR_FALSE;
}
// Determine if the Memory cache is available...
if (mMemCache) {
mMemCache->GetEnabled(&memCacheEnabled);
// Ensure that the cache is initialized
if ((mMemCacheCapacity == (PRUint32)-1) || (mMemCacheCapacity == 0))
memCacheEnabled = PR_FALSE;
}
// The CACHE_AS_FILE flag requires the Disk cache...
if (aFlags & CACHE_AS_FILE) {
if (!diskCacheEnabled)
return NS_ERROR_NOT_AVAILABLE;
cache = mDiskCache;
spaceManager = mDiskSpaceManager;
}
// The BYPASS_PERSISTANT_CACHE flag requires the Memory cache
else if (aFlags & BYPASS_PERSISTENT_CACHE) {
if (!memCacheEnabled)
return NS_ERROR_NOT_AVAILABLE;
cache = mMemCache;
spaceManager = mMemSpaceManager;
}
// Use the Disk cache if it is available...
else if (diskCacheEnabled) {
cache = mDiskCache;
spaceManager = mDiskSpaceManager;
}
// Otherwise use the memory cache if it is available...
else if (memCacheEnabled) {
cache = mMemCache;
spaceManager = mMemSpaceManager;
}
// No caches are available, so fail !!!
else {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::GetCachedNetData(const char *aUriSpec, const char *aSecondaryKey,
PRUint32 aSecondaryKeyLength,
PRUint32 aFlags, nsICachedNetData* *aResult)
{
nsCachedNetData *cachedData;
nsresult rv;
nsINetDataCache *cache;
nsReplacementPolicy *spaceManager;
rv = GetCacheAndReplacementPolicy( aFlags, cache, spaceManager );
if (NS_FAILED(rv))
return rv;
// Construct the cache key by appending the secondary key to the URI spec
nsCAutoString cacheKey(aUriSpec);
// Insert a newline between URI spec and secondary key
if (aSecondaryKey) {
cacheKey += '\n';
cacheKey.Append(aSecondaryKey, aSecondaryKeyLength);
}
nsCStringKey key(cacheKey);
cachedData = (nsCachedNetData*)mActiveCacheRecords->Get(&key);
// There is no existing instance of nsCachedNetData for this URL.
// Make one from the corresponding record in the cache module.
if (cachedData) {
NS_ASSERTION(cache == cachedData->mCache,
"Cannot yet handle simultaneously active requests for the "
"same URL using different caches");
NS_ADDREF(cachedData);
} else {
rv = spaceManager->GetCachedNetData(cacheKey.get(), cacheKey.Length(),
cache, &cachedData);
if (NS_FAILED(rv)) return rv;
mActiveCacheRecords->Put(&key, cachedData);
}
*aResult = cachedData;
return NS_OK;
}
// Remove this cache entry from the list of active ones
nsresult
nsCacheManager::NoteDormant(nsCachedNetData* aEntry)
{
nsresult rv;
PRUint32 keyLength;
char* key;
nsCOMPtr<nsINetDataCacheRecord> record;
NS_ASSERTION(gCacheManager, "Cache Manager used after shutdown!");
if (!gCacheManager) return NS_ERROR_UNEXPECTED;
rv = aEntry->GetRecord(getter_AddRefs(record));
if (NS_FAILED(rv)) return rv;
rv = record->GetKey(&keyLength, &key);
if (NS_FAILED(rv)) return rv;
nsCStringKey hashTableKey(key, keyLength);
gCacheManager->mActiveCacheRecords->Remove(&hashTableKey);
nsMemory::Free( key );
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::Contains(const char *aUriSpec, const char *aSecondaryKey,
PRUint32 aSecondaryKeyLength,
PRUint32 aFlags, PRBool *aResult)
{
nsINetDataCache *cache;
nsReplacementPolicy *spaceManager;
nsCachedNetData *cachedData;
nsresult rv = GetCacheAndReplacementPolicy( aFlags, cache, spaceManager );
if ( NS_FAILED ( rv ) )
return rv;
// Construct the cache key by appending the secondary key to the URI spec
nsCAutoString cacheKey(aUriSpec);
// Insert a newline between URI spec and secondary key
if (aSecondaryKey) {
cacheKey += '\n';
cacheKey.Append(aSecondaryKey, aSecondaryKeyLength);
}
// Locate the record using (URI + secondary key)
nsCStringKey key(cacheKey);
cachedData = (nsCachedNetData*)mActiveCacheRecords->Get(&key);
if (cachedData && (cache == cachedData->mCache)) {
*aResult = PR_TRUE;
return NS_OK;
} else {
// No active cache entry, see if there is a dormant one
return cache->Contains(cacheKey.get(), cacheKey.Length(), aResult);
}
}
NS_IMETHODIMP
nsCacheManager::GetNumEntries(PRUint32 *aNumEntries)
{
nsresult rv;
nsCOMPtr<nsISimpleEnumerator> iterator;
nsCOMPtr<nsISupports> cacheSupports;
nsCOMPtr<nsINetDataCache> cache;
PRUint32 totalEntries = 0;
rv = NewCacheModuleIterator(getter_AddRefs(iterator));
if (NS_FAILED(rv)) return rv;
while (1) {
PRBool notDone;
rv = iterator->HasMoreElements(&notDone);
if (NS_FAILED(rv)) return rv;
if (!notDone)
break;
iterator->GetNext(getter_AddRefs(cacheSupports));
cache = do_QueryInterface(cacheSupports);
PRUint32 numEntries;
rv = cache->GetNumEntries(&numEntries);
if (NS_FAILED(rv)) return rv;
totalEntries += numEntries;
}
*aNumEntries = totalEntries;
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::NewCacheEntryIterator(nsISimpleEnumerator* *aResult)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
class CacheEnumerator : public nsISimpleEnumerator
{
public:
CacheEnumerator(nsINetDataCache* aFirstCache):mCache(aFirstCache)
{ NS_INIT_REFCNT(); }
virtual ~CacheEnumerator() {};
NS_DECL_ISUPPORTS
NS_IMETHODIMP
HasMoreElements(PRBool* aMoreElements) {
*aMoreElements = (mCache != 0);
return NS_OK;
}
NS_IMETHODIMP
GetNext(nsISupports* *aSupports) {
*aSupports = mCache;
if (!mCache)
return NS_ERROR_FAILURE;
NS_ADDREF(*aSupports);
nsCOMPtr<nsINetDataCache> nextCache;
nsresult rv = mCache->GetNextCache(getter_AddRefs(nextCache));
mCache = nextCache;
return rv;
}
private:
nsCOMPtr<nsINetDataCache> mCache;
};
NS_IMPL_ISUPPORTS1(CacheEnumerator, nsISimpleEnumerator)
NS_IMETHODIMP
nsCacheManager::NewCacheModuleIterator(nsISimpleEnumerator* *aResult)
{
*aResult = new CacheEnumerator(mCacheSearchChain);
if (!*aResult)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::RemoveAll(void)
{
nsresult rv, result;
nsCOMPtr<nsISimpleEnumerator> iterator;
nsCOMPtr<nsINetDataCache> cache;
nsCOMPtr<nsISupports> iSupports;
result = NS_OK;
rv = NewCacheModuleIterator(getter_AddRefs(iterator));
if (NS_FAILED(rv)) return rv;
while (1) {
PRBool notDone;
rv = iterator->HasMoreElements(&notDone);
if (NS_FAILED(rv)) return rv;
if (!notDone)
break;
iterator->GetNext(getter_AddRefs(iSupports));
cache = do_QueryInterface(iSupports);
PRUint32 cacheFlags;
rv = cache->GetFlags(&cacheFlags);
if (NS_FAILED(rv)) return rv;
if ((cacheFlags & nsINetDataCache::READ_ONLY) == 0) {
rv = cache->RemoveAll();
if (NS_FAILED(rv))
result = rv;
}
}
return result;
}
NS_IMETHODIMP
nsCacheManager::Clear( PRUint32 aCacheToClear )
{
nsresult rv = NS_OK;
if ( aCacheToClear & ALL_CACHES )
{
return RemoveAll();
}
if ( ( aCacheToClear & MEM_CACHE) && mMemCache.get() )
{
rv = mMemCache->RemoveAll();
if( NS_FAILED ( rv ) )
return rv;
}
if ( (aCacheToClear & FILE_CACHE) && mDiskCache.get() )
{
rv = mDiskCache->RemoveAll();
if( NS_FAILED ( rv ) )
return rv;
}
if ( ( aCacheToClear & FLAT_CACHE) && mFlatCache.get() )
{
rv = mFlatCache->RemoveAll();
if( NS_FAILED ( rv ) )
return rv;
}
return rv;
}
nsresult
nsCacheManager::LimitMemCacheSize()
{
nsresult rv;
nsReplacementPolicy* spaceManager;
NS_ASSERTION(gCacheManager, "No cache manager");
spaceManager = gCacheManager->mMemSpaceManager;
PRUint32 occupancy;
rv = spaceManager->GetStorageInUse(&occupancy);
if (NS_FAILED(rv)) return rv;
PRUint32 memCacheCapacity = gCacheManager->mMemCacheCapacity;
if (occupancy > CACHE_HIGH_WATER_MARK(memCacheCapacity))
return spaceManager->Evict(CACHE_LOW_WATER_MARK(memCacheCapacity));
return NS_OK;
}
nsresult
nsCacheManager::LimitDiskCacheSize(PRBool skipCheck)
{
nsresult rv;
nsReplacementPolicy* spaceManager;
NS_ASSERTION(gCacheManager, "No cache manager");
spaceManager = gCacheManager->mDiskSpaceManager;
PRUint32 occupancy;
rv = spaceManager->GetStorageInUse(&occupancy);
if (NS_FAILED(rv)) return rv;
PRUint32 diskCacheCapacity = gCacheManager->mDiskCacheCapacity;
if (skipCheck)
{
if (occupancy > CACHE_HIGH_WATER_MARK(diskCacheCapacity))
{
gCacheManagerNeedToEvict = PR_FALSE;
return spaceManager->Evict(CACHE_LOW_WATER_MARK(diskCacheCapacity));
}
}
else
gCacheManagerNeedToEvict = PR_TRUE;
return NS_OK;
}
nsresult
nsCacheManager::LimitCacheSize()
{
nsresult rv;
rv = LimitDiskCacheSize();
if (NS_FAILED(rv)) return rv;
rv = LimitMemCacheSize();
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::SetMemCacheCapacity(PRUint32 aCapacity)
{
mMemCacheCapacity = aCapacity;
LimitCacheSize();
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::GetMemCacheCapacity(PRUint32* aCapacity)
{
NS_ENSURE_ARG_POINTER(aCapacity);
*aCapacity = mMemCacheCapacity;
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::SetDiskCacheCapacity(PRUint32 aCapacity)
{
mDiskCacheCapacity = aCapacity;
LimitCacheSize();
return NS_OK;
}
NS_IMETHODIMP
nsCacheManager::GetDiskCacheCapacity(PRUint32* aCapacity)
{
NS_ENSURE_ARG_POINTER(aCapacity);
*aCapacity = mDiskCacheCapacity;
return NS_OK;
}
//
// nsIObserver methods
//
NS_IMETHODIMP
nsCacheManager::Observe(nsISupports *aSubject,
const PRUnichar *aTopic,
const PRUnichar *aSomeData)
{
nsAutoString memPressure(NS_MEMORY_PRESSURE_TOPIC);
if (memPressure.EqualsWithConversion(aTopic)) {
(void) Clear(MEM_CACHE);
//
// Even if clearing a cache fails, do not pass the failure out to
// the observer service...
//
}
return NS_OK;
}