Files
Mozilla/mozilla/netwerk/cache/src/nsCacheService.cpp
darin%netscape.com 59d5a4317d Fixes bug 85054 "Hard Drive grinds when disk cache set to zero."
patch=gordon, r=gagan, sr=darin


git-svn-id: svn://10.0.0.236/trunk@98119 18797224-902f-48f8-a5cc-f745e15eee43
2001-06-28 01:30:26 +00:00

1427 lines
43 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* 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 nsCacheService.cpp, released February 10, 2001.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are
* Copyright (C) 2001 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Gordon Sheridan, 10-February-2001
*/
#include "nsCache.h"
#include "nsCacheService.h"
#include "nsCacheRequest.h"
#include "nsCacheEntry.h"
#include "nsCacheEntryDescriptor.h"
#include "nsCacheDevice.h"
#include "nsDiskCacheDevice.h"
#include "nsMemoryCacheDevice.h"
#include "nsICacheVisitor.h"
#include "nsAutoLock.h"
#include "nsIEventQueue.h"
#include "nsIObserverService.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIPrefBranchInternal.h"
#include "nsIPref.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
/******************************************************************************
* nsCacheProfilePrefObserver
*****************************************************************************/
#ifdef XP_MAC
#pragma mark nsCacheProfilePrefObserver
#endif
#define DISK_CACHE_ENABLE_PREF "browser.cache.disk.enable"
#define DISK_CACHE_DIR_PREF "browser.cache.disk.directory"
#define DISK_CACHE_CAPACITY_PREF "browser.cache.disk.capacity"
#define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable"
#define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity"
class nsCacheProfilePrefObserver : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
nsCacheProfilePrefObserver()
: mHaveProfile(PR_FALSE)
, mDiskCacheEnabled(PR_FALSE)
, mDiskCacheCapacity(0)
, mMemoryCacheEnabled(PR_TRUE)
, mMemoryCacheCapacity(4 * 1024 * 1024)
{
NS_INIT_ISUPPORTS();
}
virtual ~nsCacheProfilePrefObserver() {}
nsresult Install();
nsresult Remove();
nsresult ReadPrefs();
PRBool DiskCacheEnabled();
PRInt32 DiskCacheCapacity() { return mDiskCacheCapacity; }
nsILocalFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
PRBool MemoryCacheEnabled();
PRInt32 MemoryCacheCapacity() { return mMemoryCacheCapacity; }
private:
PRBool mHaveProfile;
PRBool mDiskCacheEnabled;
PRInt32 mDiskCacheCapacity;
nsCOMPtr<nsILocalFile> mDiskCacheParentDirectory;
PRBool mMemoryCacheEnabled;
PRInt32 mMemoryCacheCapacity;
};
NS_IMPL_ISUPPORTS1(nsCacheProfilePrefObserver, nsIObserver);
nsresult
nsCacheProfilePrefObserver::Install()
{
nsresult rv, rv2 = NS_OK;
// install profile-change observer
nsCOMPtr<nsIObserverService> observerService = do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
NS_ENSURE_ARG(observerService);
rv = observerService->AddObserver(this, NS_LITERAL_STRING("profile-before-change").get());
if (NS_FAILED(rv)) rv2 = rv;
rv = observerService->AddObserver(this, NS_LITERAL_STRING("profile-after-change").get());
if (NS_FAILED(rv)) rv2 = rv;
// install xpcom shutdown observer
rv = observerService->AddObserver(this, NS_LITERAL_STRING(NS_XPCOM_SHUTDOWN_OBSERVER_ID).get());
if (NS_FAILED(rv)) rv2 = rv;
// install preferences observer
nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIPrefBranchInternal> prefInternal = do_QueryInterface(prefService, &rv);
if (NS_FAILED(rv)) return rv;
rv = prefInternal->AddObserver(MEMORY_CACHE_ENABLE_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->AddObserver(DISK_CACHE_ENABLE_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->AddObserver(DISK_CACHE_DIR_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->AddObserver(DISK_CACHE_CAPACITY_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->AddObserver(MEMORY_CACHE_CAPACITY_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = ReadPrefs();
return NS_SUCCEEDED(rv) ? rv2 : rv;
}
nsresult
nsCacheProfilePrefObserver::Remove()
{
nsresult rv, rv2 = NS_OK;
// remove Observer Service observers
nsCOMPtr<nsIObserverService> observerService = do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
NS_ENSURE_ARG(observerService);
rv = observerService->RemoveObserver(this, NS_LITERAL_STRING("profile-before-change").get());
if (NS_FAILED(rv)) rv2 = rv;
rv = observerService->RemoveObserver(this, NS_LITERAL_STRING("profile-after-change").get());
if (NS_FAILED(rv)) rv2 = rv;
rv = observerService->RemoveObserver(this, NS_LITERAL_STRING(NS_XPCOM_SHUTDOWN_OBSERVER_ID).get());
if (NS_FAILED(rv)) rv2 = rv;
// remove Pref Service observers
nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIPrefBranchInternal> prefInternal = do_QueryInterface(prefService, &rv);
if (NS_FAILED(rv)) return rv;
// remove Disk cache pref observers
rv = prefInternal->RemoveObserver(DISK_CACHE_ENABLE_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->RemoveObserver(DISK_CACHE_CAPACITY_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->RemoveObserver(DISK_CACHE_DIR_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
// remove Memory cache pref observers
rv = prefInternal->RemoveObserver(MEMORY_CACHE_ENABLE_PREF, this);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefInternal->RemoveObserver(MEMORY_CACHE_CAPACITY_PREF, this);
// if (NS_FAILED(rv)) rv2 = rv;
return NS_SUCCEEDED(rv) ? rv2 : rv;
}
NS_IMETHODIMP
nsCacheProfilePrefObserver::Observe(nsISupports * subject,
const PRUnichar * topic,
const PRUnichar * data)
{
#ifdef DEBUG
printf("### nsCacheProfilePrefObserver::Observe [topic=%s data=%s]\n",
NS_ConvertUCS2toUTF8(topic).get(),
NS_ConvertUCS2toUTF8(data).get());
#endif
nsresult rv;
if (NS_LITERAL_STRING(NS_XPCOM_SHUTDOWN_OBSERVER_ID).Equals(topic)) {
// xpcom going away, shutdown cache service
if (nsCacheService::GlobalInstance())
nsCacheService::GlobalInstance()->Shutdown();
} else if (NS_LITERAL_STRING("profile-before-change").Equals(topic)) {
// profile before change
mHaveProfile = PR_FALSE;
// XXX shutdown devices
if (NS_LITERAL_STRING("shutdown-cleanse").Equals(data)) {
// XXX we need to delete the disk cache
// printf("cache observer: shutdown-cleanse\n");
}
} else if (NS_LITERAL_STRING("profile-after-change").Equals(topic)) {
// profile after change
mHaveProfile = PR_TRUE;
ReadPrefs();
nsCacheService::ProfileChanged();
} else if (NS_LITERAL_STRING("nsPref:changed").Equals(topic)) {
if (!mHaveProfile) return NS_OK;
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject, &rv);
if (NS_FAILED(rv))
return rv;
// which preference changed?
if (NS_LITERAL_STRING(DISK_CACHE_ENABLE_PREF).Equals(data)) {
rv = prefBranch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
if (NS_FAILED(rv)) return rv;
nsCacheService::SetDiskCacheEnabled(DiskCacheEnabled());
} else if (NS_LITERAL_STRING(DISK_CACHE_CAPACITY_PREF).Equals(data)) {
PRInt32 capacity = 0;
rv = prefBranch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
if (NS_FAILED(rv)) return rv;
mDiskCacheCapacity = (PRUint32) PR_MAX(0, capacity);
nsCacheService::SetDiskCacheCapacity(mDiskCacheCapacity);
#if 0
} else if (NS_LITERAL_STRING(DISK_CACHE_DIR_PREF).Equals(data)) {
// XXX we probaby don't want to respond to this pref except after profile changes
// XXX ideally, there should be somekind of user notification that the pref change
// XXX won't take effect until the next time the profile changes (browser launch)
#endif
} else if (NS_LITERAL_STRING(MEMORY_CACHE_ENABLE_PREF).Equals(data)) {
rv = prefBranch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
if (NS_FAILED(rv)) return rv;
nsCacheService::SetMemoryCacheEnabled(MemoryCacheEnabled());
} else if (NS_LITERAL_STRING(MEMORY_CACHE_CAPACITY_PREF).Equals(data)) {
PRInt32 capacity = 0;
rv = prefBranch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF, &capacity);
if (NS_FAILED(rv)) return rv;
mMemoryCacheCapacity = (PRUint32) PR_MAX(0, capacity);
nsCacheService::SetMemoryCacheCapacity(mMemoryCacheCapacity);
}
}
return NS_OK;
}
nsresult
nsCacheProfilePrefObserver::ReadPrefs()
{
nsresult rv, rv2 = NS_OK;
PRInt32 capacity = 0;
nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(prefService, &rv);
if (NS_FAILED(rv)) return rv;
// read disk cache device prefs
rv = prefBranch->GetBoolPref(DISK_CACHE_ENABLE_PREF, &mDiskCacheEnabled);
if (NS_FAILED(rv)) rv2 = rv;
rv = prefBranch->GetIntPref(DISK_CACHE_CAPACITY_PREF, &capacity);
if (NS_FAILED(rv)) rv2 = rv;
mDiskCacheCapacity = (PRUint32) PR_MAX(0, capacity);
(void) prefBranch->GetComplexValue(DISK_CACHE_DIR_PREF, // ignore error
NS_GET_IID(nsILocalFile),
getter_AddRefs(mDiskCacheParentDirectory));
if (!mDiskCacheParentDirectory) {
nsCOMPtr<nsIFile> directory;
// try to get the profile directory (there may not be a profile yet)
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(directory));
#if DEBUG
if (NS_FAILED(rv)) {
// use current process directory during development
rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, getter_AddRefs(directory));
}
#endif
if (directory)
mDiskCacheParentDirectory = do_QueryInterface(directory, &rv);
}
// read memory cache device prefs
rv = prefBranch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, &mMemoryCacheEnabled);
if (NS_FAILED(rv)) rv2 = rv;
capacity = 0;
rv = prefBranch->GetIntPref(MEMORY_CACHE_CAPACITY_PREF, &capacity);
mMemoryCacheCapacity = (PRUint32) PR_MAX(0, capacity);
return NS_SUCCEEDED(rv) ? rv2 : rv;
}
PRBool
nsCacheProfilePrefObserver::DiskCacheEnabled()
{
if ((mDiskCacheCapacity == 0) || (!mDiskCacheParentDirectory)) return PR_FALSE;
return mDiskCacheEnabled;
}
PRBool
nsCacheProfilePrefObserver::MemoryCacheEnabled()
{
if (mMemoryCacheCapacity == 0) return PR_FALSE;
return mMemoryCacheEnabled;
}
/******************************************************************************
* nsCacheService
*****************************************************************************/
#ifdef XP_MAC
#pragma mark -
#pragma mark nsCacheService
#endif
nsCacheService * nsCacheService::gService = nsnull;
//NS_IMPL_THREADSAFE_ISUPPORTS2(nsCacheService, nsICacheService, nsIObserver)
NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService);
nsCacheService::nsCacheService()
: mCacheServiceLock(nsnull),
mEnableMemoryDevice(PR_TRUE),
mEnableDiskDevice(PR_TRUE),
mMemoryDevice(nsnull),
mDiskDevice(nsnull),
mTotalEntries(0),
mCacheHits(0),
mCacheMisses(0),
mMaxKeyLength(0),
mMaxDataSize(0),
mMaxMetaSize(0),
mDeactivateFailures(0),
mDeactivatedUnboundEntries(0)
{
NS_INIT_REFCNT();
NS_ASSERTION(gService==nsnull, "multiple nsCacheService instances!");
gService = this;
// create list of cache devices
PR_INIT_CLIST(&mDoomedEntries);
}
nsCacheService::~nsCacheService()
{
if (mCacheServiceLock) // Shutdown hasn't been called yet.
(void) Shutdown();
gService = nsnull;
}
NS_IMETHODIMP
nsCacheService::Init()
{
nsresult rv;
NS_ASSERTION(mCacheServiceLock== nsnull, "nsCacheService already initialized.");
if (mCacheServiceLock)
return NS_ERROR_ALREADY_INITIALIZED;
CACHE_LOG_INIT();
mCacheServiceLock = PR_NewLock();
if (mCacheServiceLock == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
// initialize hashtable for active cache entries
rv = mActiveEntries.Init();
if (NS_FAILED(rv)) goto error;
// get references to services we'll be using frequently
mEventQService = do_GetService(NS_EVENTQUEUESERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
mProxyObjectManager = do_GetService(NS_XPCOMPROXY_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
mObserver = new nsCacheProfilePrefObserver();
if (!mObserver) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(mObserver);
mObserver->Install();
mEnableDiskDevice = mObserver->DiskCacheEnabled();
mEnableMemoryDevice = mObserver->MemoryCacheEnabled();
rv = CreateMemoryDevice();
if (NS_FAILED(rv) && (rv != NS_ERROR_NOT_AVAILABLE)) goto error;
return NS_OK;
error:
(void)Shutdown();
return rv;
}
NS_IMETHODIMP
nsCacheService::Shutdown()
{
NS_ASSERTION(mCacheServiceLock != nsnull,
"can't shutdown nsCacheService unless it has been initialized.");
if (mCacheServiceLock) {
// XXX this is not sufficient
PRLock * tempLock = mCacheServiceLock;
mCacheServiceLock = nsnull;
#if defined(PR_LOGGING)
LogCacheStatistics();
#endif
mObserver->Remove();
NS_RELEASE(mObserver);
// Clear entries
ClearDoomList();
ClearActiveEntries();
// deallocate memory and disk caches
delete mMemoryDevice;
mMemoryDevice = nsnull;
delete mDiskDevice;
mDiskDevice = nsnull;
PR_DestroyLock(tempLock);
}
return NS_OK;
}
NS_METHOD
nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
{
nsresult rv;
if (aOuter != nsnull)
return NS_ERROR_NO_AGGREGATION;
nsCacheService * cacheService = new nsCacheService();
if (cacheService == nsnull)
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,
PRBool streamBased,
nsICacheSession **result)
{
*result = nsnull;
if ((this == nsnull) || !(mEnableDiskDevice || mEnableMemoryDevice))
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)
{
return EvictEntriesForClient(session->ClientID()->get(),
session->StoragePolicy());
}
nsresult
nsCacheService::EvictEntriesForClient(const char * clientID,
nsCacheStoragePolicy storagePolicy)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
nsresult rv = NS_OK;
if (storagePolicy == nsICache::STORE_ANYWHERE ||
storagePolicy == nsICache::STORE_ON_DISK) {
if (mEnableDiskDevice) {
if (!mDiskDevice) {
rv = CreateDiskDevice();
if (NS_FAILED(rv)) return rv;
}
rv = mDiskDevice->EvictEntries(clientID);
if (NS_FAILED(rv)) return rv;
}
}
if (storagePolicy == nsICache::STORE_ANYWHERE ||
storagePolicy == nsICache::STORE_IN_MEMORY) {
if (mEnableMemoryDevice) {
rv = mMemoryDevice->EvictEntries(clientID);
if (NS_FAILED(rv)) return rv;
}
}
return NS_OK;
}
NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
{
nsAutoLock lock(mCacheServiceLock);
if (!(mEnableDiskDevice || mEnableMemoryDevice))
return NS_ERROR_NOT_AVAILABLE;
// XXX record the fact that a visitation is in progress, i.e. keep
// list of visitors in progress.
nsresult rv = NS_OK;
if (mEnableMemoryDevice) {
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;
}
// XXX notify any shutdown process that visitation is complete for THIS visitor.
// XXX keep queue of visitors
return NS_OK;
}
NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy)
{
return EvictEntriesForClient(nsnull, storagePolicy);
}
/**
* Internal Methods
*/
nsresult
nsCacheService::CreateDiskDevice()
{
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());
nsresult rv = mDiskDevice->Init();
if (NS_FAILED(rv)) {
#if DEBUG
printf("###\n");
printf("### mDiskDevice->Init() failed (0x%.8x)\n", rv);
printf("### - disabling disk cache for this session.\n");
printf("###\n");
#endif
mEnableDiskDevice = PR_FALSE;
delete mDiskDevice;
mDiskDevice = nsnull;
}
return rv;
}
nsresult
nsCacheService::CreateMemoryDevice()
{
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
mMemoryDevice->SetCapacity(mObserver->MemoryCacheCapacity());
nsresult rv = mMemoryDevice->Init();
if (NS_FAILED(rv)) {
// XXX log error
delete mMemoryDevice;
mMemoryDevice = nsnull;
}
return rv;
}
nsresult
nsCacheService::CreateRequest(nsCacheSession * session,
const char * clientKey,
nsCacheAccessMode accessRequested,
PRBool blockingMode,
nsICacheListener * listener,
nsCacheRequest ** request)
{
NS_ASSERTION(request, "CommonOpenCacheEntry: request or entry is null");
nsCString * key = new nsCString(*session->ClientID());
if (!key)
return NS_ERROR_OUT_OF_MEMORY;
key->Append(":");
key->Append(clientKey);
if (mMaxKeyLength < key->Length()) mMaxKeyLength = key->Length();
// create request
*request = new nsCacheRequest(key, listener, accessRequested, blockingMode, session);
if (!*request) {
delete key;
return NS_ERROR_OUT_OF_MEMORY;
}
if (!listener) return NS_OK; // we're sync, we're done.
// get the nsIEventQueue for the request's thread
(*request)->mThread = PR_GetCurrentThread();
return NS_OK;
}
nsresult
nsCacheService::NotifyListener(nsCacheRequest * request,
nsICacheEntryDescriptor * descriptor,
nsCacheAccessMode accessGranted,
nsresult error)
{
nsresult rv;
nsCOMPtr<nsICacheListener> listenerProxy;
NS_ASSERTION(request->mThread, "no thread set in async request!");
nsCOMPtr<nsIEventQueue> eventQ;
mEventQService->GetThreadEventQueue(request->mThread,
getter_AddRefs(eventQ));
rv = mProxyObjectManager->GetProxyForObject(eventQ,
NS_GET_IID(nsICacheListener),
request->mListener,
PROXY_ASYNC|PROXY_ALWAYS,
getter_AddRefs(listenerProxy));
if (NS_FAILED(rv)) return rv;
return listenerProxy->OnCacheEntryAvailable(descriptor, accessGranted, error);
}
nsresult
nsCacheService::ProcessRequest(nsCacheRequest * request,
PRBool calledFromOpenCacheEntry,
nsICacheEntryDescriptor ** result)
{
// !!! must be called with mCacheServiceLock held !!!
nsresult rv;
nsCacheEntry * entry = nsnull;
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
if (result) *result = nsnull;
while(1) { // Activate entry loop
rv = ActivateEntry(request, &entry); // 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->mListener) // async exits - validate, doom, or close will resume
return rv;
if (request->IsBlocking()) {
PR_Unlock(mCacheServiceLock);
rv = request->WaitForValidation();
PR_Lock(mCacheServiceLock);
}
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
}
nsCOMPtr<nsICacheEntryDescriptor> descriptor;
if (NS_SUCCEEDED(rv))
rv = entry->CreateDescriptor(request, accessGranted, getter_AddRefs(descriptor));
if (request->mListener) { // Asynchronous
if (NS_FAILED(rv) && calledFromOpenCacheEntry)
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
NS_IF_ADDREF(*result = descriptor);
}
return rv;
}
nsresult
nsCacheService::OpenCacheEntry(nsCacheSession * session,
const char * key,
nsCacheAccessMode accessRequested,
PRBool blockingMode,
nsICacheListener * listener,
nsICacheEntryDescriptor ** result)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
if (result)
*result = nsnull;
nsCacheRequest * request = nsnull;
nsAutoLock lock(mCacheServiceLock);
nsresult rv = CreateRequest(session,
key,
accessRequested,
blockingMode,
listener,
&request);
if (NS_FAILED(rv)) return rv;
rv = ProcessRequest(request, PR_TRUE, result);
// delete requests that have completed
if (!(listener && (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
delete request;
return rv;
}
nsresult
nsCacheService::ActivateEntry(nsCacheRequest * request,
nsCacheEntry ** result)
{
nsresult rv = NS_OK;
NS_ASSERTION(request != nsnull, "ActivateEntry called with no request");
if (result) *result = nsnull;
if ((!request) || (!result)) return NS_ERROR_NULL_POINTER;
// search active entries (including those not bound to device)
nsCacheEntry *entry = mActiveEntries.GetEntry(request->mKey);
if (!entry) {
// search cache devices for entry
entry = SearchCacheDevices(request->mKey, request->StoragePolicy());
if (entry) entry->MarkInitialized();
}
if (entry) {
++mCacheHits;
entry->Fetched();
} else {
++mCacheMisses;
}
if (!entry && !(request->AccessRequested() & nsICache::ACCESS_WRITE)) {
// this is a READ-ONLY request
rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
goto error;
}
if (entry &&
((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
(entry->mExpirationTime &&
entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
request->WillDoomEntriesIfExpired())))
{
// this is FORCE-WRITE request or the entry has expired
rv = DoomEntry_Locked(entry);
if (NS_FAILED(rv)) {
// XXX what to do? Increment FailedDooms counter?
}
entry = nsnull;
}
if (!entry) {
entry = new nsCacheEntry(request->mKey,
request->IsStreamBased(),
request->StoragePolicy());
if (!entry)
return NS_ERROR_OUT_OF_MEMORY;
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;
entry->MarkActive(); // mark entry active, because it's now in mActiveEntries
}
*result = entry;
return NS_OK;
error:
*result = nsnull;
if (entry) {
// XXX clean up
delete entry;
}
return rv;
}
nsCacheEntry *
nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy)
{
nsCacheEntry * entry = nsnull;
if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
if (mEnableMemoryDevice)
entry = mMemoryDevice->FindEntry(key);
}
if (!entry &&
((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
if (mEnableDiskDevice) {
if (!mDiskDevice) {
nsresult rv = CreateDiskDevice();
if (NS_FAILED(rv))
return nsnull;
}
entry = mDiskDevice->FindEntry(key);
}
}
return entry;
}
nsCacheDevice *
nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry)
{
nsCacheDevice * device = entry->CacheDevice();
if (device) return device;
nsresult rv = NS_OK;
if (entry->IsStreamData() && entry->IsAllowedOnDisk() && mEnableDiskDevice) {
// this is the default
if (!mDiskDevice) {
rv = CreateDiskDevice(); // ignore the error (check for mDiskDevice instead)
}
if (mDiskDevice) {
entry->MarkBinding(); // XXX
rv = mDiskDevice->BindEntry(entry);
entry->ClearBinding(); // XXX
if (NS_SUCCEEDED(rv))
device = mDiskDevice;
}
}
// if we can't use mDiskDevice, try mMemoryDevice
if (!device && mEnableMemoryDevice) {
NS_ASSERTION(entry->IsAllowedInMemory(), "oops.. bad flags");
entry->MarkBinding(); // XXX
rv = mMemoryDevice->BindEntry(entry);
entry->ClearBinding(); // XXX
if (NS_SUCCEEDED(rv))
device = mMemoryDevice;
}
if (device == nsnull) return nsnull;
entry->SetCacheDevice(device);
return device;
}
nsresult
nsCacheService::ValidateEntry(nsCacheEntry * entry)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
nsCacheDevice * device = EnsureEntryHasDevice(entry);
if (!device) return NS_ERROR_UNEXPECTED; // XXX need better error here
entry->MarkValid();
nsresult rv = ProcessPendingRequests(entry);
NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
// XXX what else can be done?
return rv;
}
nsresult
nsCacheService::DoomEntry(nsCacheEntry * entry)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
return DoomEntry_Locked(entry);
}
nsresult
nsCacheService::DoomEntry_Locked(nsCacheEntry * entry)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
if (entry->IsDoomed()) return NS_OK;
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);
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);
// 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;
}
static void* PR_CALLBACK
EventHandler(PLEvent *self)
{
nsISupports * object = (nsISupports *)PL_GetEventOwner(self);
NS_RELEASE(object);
return 0;
}
static void PR_CALLBACK
DestroyHandler(PLEvent *self)
{
delete self;
}
void
nsCacheService::ProxyObjectRelease(nsISupports * object, PRThread * thread)
{
NS_ASSERTION(gService, "nsCacheService not initialized");
NS_ASSERTION(thread, "no thread");
// XXX if thread == current thread, we could avoid posting an event,
// XXX by add this object to a queue and release it when the cache service is unlocked.
nsCOMPtr<nsIEventQueue> eventQ;
gService->mEventQService->GetThreadEventQueue(thread, getter_AddRefs(eventQ));
NS_ASSERTION(eventQ, "no event queue for thread");
if (!eventQ) return;
PLEvent * event = new PLEvent;
if (!event) {
// XXX warning
return;
}
PL_InitEvent(event, object, EventHandler, DestroyHandler);
eventQ->PostEvent(event);
}
void
nsCacheService::ProfileChanged()
{
if (!gService) return;
nsAutoLock lock(gService->mCacheServiceLock);
NS_ASSERTION(!gService->mDiskDevice, "switching cache directories not supported yet.");
gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
if (gService->mDiskDevice)
gService->mDiskDevice->SetCapacity(gService->mObserver->DiskCacheCapacity());
if (gService->mMemoryDevice)
gService->mMemoryDevice->SetCapacity(gService->mObserver->MemoryCacheCapacity());
}
void
nsCacheService::SetDiskCacheEnabled(PRBool enabled)
{
if (!gService) return;
nsAutoLock lock(gService->mCacheServiceLock);
gService->mEnableDiskDevice = enabled;
}
void
nsCacheService::SetDiskCacheCapacity(PRInt32 capacity)
{
if (!gService) return;
nsAutoLock lock(gService->mCacheServiceLock);
if (gService->mDiskDevice) {
gService->mDiskDevice->SetCapacity(capacity);
}
gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled();
}
void
nsCacheService::SetMemoryCacheEnabled(PRBool enabled)
{
if (!gService) return;
nsAutoLock lock(gService->mCacheServiceLock);
gService->mEnableMemoryDevice = enabled;
(void) gService->CreateMemoryDevice(); // allocate memory device, if necessary
if (!enabled && gService->mMemoryDevice) {
// tell memory device to evict everything
gService->mMemoryDevice->SetCapacity(0);
}
}
void
nsCacheService::SetMemoryCacheCapacity(PRInt32 capacity)
{
if (!gService) return;
nsAutoLock lock(gService->mCacheServiceLock);
if (gService->mMemoryDevice) {
gService->mMemoryDevice->SetCapacity(capacity);
}
gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled();
}
#if 0
void
nsCacheService::SetCacheDevicesEnabled(PRBool enableDisk, PRBool enableMemory)
{
if (this == nsnull) return; // NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
if (enableDisk && !mDiskDevice) {
// disk device requires lazy activation
} else if (!enableDisk && mDiskDevice) {
// XXX deactivate disk
}
if (enableMemory && !mMemoryDevice) {
// XXX enable memory cache device
} else if (!enableMemory && mMemoryDevice) {
// XXX disable memory cache device
}
}
#endif
nsresult
nsCacheService::SetCacheElement(nsCacheEntry * entry, nsISupports * element)
{
entry->SetThread(PR_GetCurrentThread());
entry->SetData(element);
entry->TouchData();
return NS_OK;
}
nsresult
nsCacheService::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
nsCacheDevice * device = EnsureEntryHasDevice(entry);
if (!device) return NS_ERROR_UNEXPECTED; // XXX need better error here
return device->OnDataSizeChange(entry, deltaSize);
}
nsresult
nsCacheService::GetTransportForEntry(nsCacheEntry * entry,
nsCacheAccessMode mode,
nsITransport ** result)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
nsCacheDevice * device = EnsureEntryHasDevice(entry);
if (!device) return NS_ERROR_UNEXPECTED; // XXX need better error here
return device->GetTransportForEntry(entry, mode, result);
}
void
nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
{
NS_ASSERTION(this != nsnull, "CloseDescriptor called with no cache service!");
if (this == nsnull) return;
nsAutoLock lock(mCacheServiceLock);
// ask entry to remove descriptor
nsCacheEntry * entry = descriptor->CacheEntry();
PRBool stillActive = entry->RemoveDescriptor(descriptor);
nsresult rv = NS_OK;
if (!entry->IsValid()) {
rv = ProcessPendingRequests(entry);
}
if (!stillActive) {
DeactivateEntry(entry);
}
}
nsresult
nsCacheService::GetFileForEntry(nsCacheEntry * entry,
nsIFile ** result)
{
if (this == nsnull) return NS_ERROR_NOT_AVAILABLE;
nsAutoLock lock(mCacheServiceLock);
nsCacheDevice * device = EnsureEntryHasDevice(entry);
if (!device) return NS_ERROR_UNEXPECTED; // XXX need better error here
return device->GetFileForEntry(entry, result);
}
void
nsCacheService::DeactivateEntry(nsCacheEntry * entry)
{
nsresult rv = NS_OK;
NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
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);
entry->MarkInactive();
} else {
// XXX bad state
}
}
nsCacheDevice * device = entry->CacheDevice();
if (device) {
rv = device->DeactivateEntry(entry);
if (NS_FAILED(rv)) {
// increment deactivate failure count
++mDeactivateFailures;
}
} else {
// increment deactivating unbound entry statistic
++mDeactivatedUnboundEntries;
delete entry; // because no one else will
}
}
nsresult
nsCacheService::ProcessPendingRequests(nsCacheEntry * entry)
{
nsresult rv = NS_OK;
nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
nsCacheRequest * nextRequest;
PRBool newWriter = PR_FALSE;
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 = PR_TRUE;
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
}
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
while (request != &entry->mRequestQ) {
nextRequest = (nsCacheRequest *)PR_NEXT_LINK(request);
if (request->mListener) {
// Async request
PR_REMOVE_AND_INIT_LINK(request);
if (entry->IsDoomed()) {
rv = ProcessRequest(request, PR_FALSE, nsnull);
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
nsCOMPtr<nsICacheEntryDescriptor> descriptor;
rv = entry->CreateDescriptor(request,
accessGranted,
getter_AddRefs(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 {
// XXX bad state
}
} else {
// Synchronous request
request->WakeUp();
}
if (newWriter) break; // process remaining requests after validation
request = nextRequest;
}
return NS_OK;
}
void
nsCacheService::ClearPendingRequests(nsCacheEntry * entry)
{
nsCacheRequest * request = (nsCacheRequest *)PR_LIST_HEAD(&entry->mRequestQ);
while (request != &entry->mRequestQ) {
nsCacheRequest * next = (nsCacheRequest *)PR_NEXT_LINK(request);
// XXX we're just dropping these on the floor for now...definitely wrong.
PR_REMOVE_AND_INIT_LINK(request);
delete request;
request = next;
}
}
void
nsCacheService::ClearDoomList()
{
nsCacheEntry * entry = (nsCacheEntry *)PR_LIST_HEAD(&mDoomedEntries);
while (entry != &mDoomedEntries) {
nsCacheEntry * next = (nsCacheEntry *)PR_NEXT_LINK(entry);
entry->DetachDescriptors();
DeactivateEntry(entry);
entry = next;
}
}
void
nsCacheService::ClearActiveEntries()
{
// XXX really we want a different finalize callback for mActiveEntries
PL_DHashTableEnumerate(&mActiveEntries.table, DeactivateAndClearEntry, nsnull);
mActiveEntries.Shutdown();
}
PLDHashOperator PR_CALLBACK
nsCacheService::DeactivateAndClearEntry(PLDHashTable * table,
PLDHashEntryHdr * hdr,
PRUint32 number,
void * arg)
{
nsCacheEntry * entry = ((nsCacheEntryHashTableEntry *)hdr)->cacheEntry;
NS_ASSERTION(entry, "### active entry = nsnull!");
gService->ClearPendingRequests(entry);
entry->DetachDescriptors();
entry->MarkInactive(); // so we don't call Remove() while we're enumerating
gService->DeactivateEntry(entry);
return PL_DHASH_REMOVE; // and continue enumerating
}
#if defined(PR_LOGGING)
void
nsCacheService::LogCacheStatistics()
{
PRUint32 hitPercentage = (PRUint32)((((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