Patch by gordon, r=bbaetz, sr=dougt. git-svn-id: svn://10.0.0.236/trunk@99543 18797224-902f-48f8-a5cc-f745e15eee43
456 lines
12 KiB
C++
456 lines
12 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 nsMemoryCacheDevice.cpp, released February 22, 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, <gordon@netscape.com>
|
|
* Patrick C. Beard <beard@netscape.com>
|
|
*/
|
|
|
|
#include "nsMemoryCacheDevice.h"
|
|
#include "nsCacheService.h"
|
|
#include "nsICacheService.h"
|
|
#include "nsIComponentManager.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIPref.h"
|
|
#include "nsICacheVisitor.h"
|
|
#include "nsITransport.h"
|
|
#include "signal.h"
|
|
|
|
|
|
static NS_DEFINE_CID(kStorageTransportCID, NS_STORAGETRANSPORT_CID);
|
|
|
|
const char *gMemoryDeviceID = "memory";
|
|
|
|
|
|
nsMemoryCacheDevice::nsMemoryCacheDevice()
|
|
: mEvictionThreshold(40 * 1024),
|
|
mHardLimit(4 * 1024 * 1024), // set default memory limit, in case prefs aren't available
|
|
mTotalSize(0),
|
|
mInactiveSize(0),
|
|
mEntryCount(0),
|
|
mMaxEntryCount(0)
|
|
{
|
|
PR_INIT_CLIST(&mEvictionList[mostLikelyToEvict]);
|
|
PR_INIT_CLIST(&mEvictionList[leastLikelyToEvict]);
|
|
}
|
|
|
|
|
|
nsMemoryCacheDevice::~nsMemoryCacheDevice()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::Init()
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = mMemCacheEntries.Init();
|
|
|
|
// set some default memory limits, in case prefs aren't available
|
|
mSoftLimit = mHardLimit * 0.9;
|
|
|
|
// XXX Register as a memory pressure observer
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::Shutdown()
|
|
{
|
|
mMemCacheEntries.Shutdown();
|
|
|
|
// evict all entries
|
|
nsCacheEntry * entry, * next;
|
|
|
|
for (int i=mostLikelyToEvict; i <= leastLikelyToEvict; ++i) {
|
|
entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
|
|
while (entry != &mEvictionList[i]) {
|
|
NS_ASSERTION(entry->IsInUse() == PR_FALSE, "### shutting down with active entries.\n");
|
|
next = (nsCacheEntry *)PR_NEXT_LINK(entry);
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
|
|
// update statistics
|
|
PRUint32 memoryRecovered = entry->Size();
|
|
mTotalSize -= memoryRecovered;
|
|
mInactiveSize -= memoryRecovered;
|
|
--mEntryCount;
|
|
|
|
delete entry;
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* we're not factoring in changes to meta data yet...
|
|
* NS_ASSERTION(mTotalSize == 0, "### mem cache leaking entries?\n");
|
|
*/
|
|
NS_ASSERTION(mInactiveSize == 0, "### mem cache leaking entries?\n");
|
|
NS_ASSERTION(mEntryCount == 0, "### mem cache leaking entries?\n");
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
const char *
|
|
nsMemoryCacheDevice::GetDeviceID()
|
|
{
|
|
return gMemoryDeviceID;
|
|
}
|
|
|
|
|
|
nsCacheEntry *
|
|
nsMemoryCacheDevice::FindEntry(nsCString * key)
|
|
{
|
|
nsCacheEntry * entry = mMemCacheEntries.GetEntry(key);
|
|
if (!entry) return nsnull;
|
|
|
|
// move entry to the tail of an eviction list
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
|
|
|
|
mInactiveSize -= entry->Size();
|
|
|
|
return entry;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::DeactivateEntry(nsCacheEntry * entry)
|
|
{
|
|
if (entry->IsDoomed()) {
|
|
#if debug
|
|
// XXX verify we've removed it from mMemCacheEntries & eviction list
|
|
#endif
|
|
// update statistics
|
|
mTotalSize -= entry->Size();
|
|
--mEntryCount;
|
|
|
|
delete entry;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCacheEntry * ourEntry = mMemCacheEntries.GetEntry(entry->Key());
|
|
NS_ASSERTION(ourEntry, "DeactivateEntry called for an entry we don't have!");
|
|
NS_ASSERTION(entry == ourEntry, "entry doesn't match ourEntry");
|
|
if (ourEntry != entry)
|
|
return NS_ERROR_INVALID_POINTER;
|
|
|
|
mInactiveSize += entry->Size();
|
|
EvictEntriesIfNecessary();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::BindEntry(nsCacheEntry * entry)
|
|
{
|
|
if (!entry->IsDoomed()) {
|
|
NS_ASSERTION(PR_CLIST_IS_EMPTY(entry),"entry is already on a list!");
|
|
|
|
// append entry to the eviction list
|
|
PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, 0)]);
|
|
|
|
// add entry to hashtable of mem cache entries
|
|
nsresult rv = mMemCacheEntries.AddEntry(entry);
|
|
if (NS_FAILED(rv)) {
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// add size of entry to memory totals
|
|
++mEntryCount;
|
|
if (mMaxEntryCount < mEntryCount) mMaxEntryCount = mEntryCount;
|
|
|
|
mTotalSize += entry->Size();
|
|
EvictEntriesIfNecessary();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nsMemoryCacheDevice::DoomEntry(nsCacheEntry * entry)
|
|
{
|
|
// XXX debug code to verify we have entry
|
|
mMemCacheEntries.RemoveEntry(entry);
|
|
|
|
// remove entry from our eviction list
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::GetTransportForEntry( nsCacheEntry * entry,
|
|
nsCacheAccessMode mode,
|
|
nsITransport ** transport )
|
|
{
|
|
NS_ENSURE_ARG_POINTER(entry);
|
|
NS_ENSURE_ARG_POINTER(transport);
|
|
|
|
nsCOMPtr<nsISupports> data;
|
|
|
|
nsresult rv = entry->GetData(getter_AddRefs(data));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
if (data)
|
|
return CallQueryInterface(data, transport);
|
|
else {
|
|
// create a new transport for this entry
|
|
rv = nsComponentManager::CreateInstance(kStorageTransportCID,
|
|
nsnull,
|
|
NS_GET_IID(nsITransport),
|
|
(void **) transport);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
entry->SetData(*transport);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::GetFileForEntry( nsCacheEntry * entry,
|
|
nsIFile ** result )
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::OnDataSizeChange( nsCacheEntry * entry, PRInt32 deltaSize)
|
|
{
|
|
if (entry->IsStreamData()) {
|
|
// we have the right to refuse or pre-evict
|
|
}
|
|
|
|
// adjust our totals
|
|
mTotalSize += deltaSize;
|
|
|
|
if (!entry->IsDoomed()) {
|
|
// move entry to the tail of the appropriate eviction list
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
PR_APPEND_LINK(entry, &mEvictionList[EvictionList(entry, deltaSize)]);
|
|
}
|
|
|
|
EvictEntriesIfNecessary();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nsMemoryCacheDevice::AdjustMemoryLimits(PRUint32 softLimit, PRUint32 hardLimit)
|
|
{
|
|
mSoftLimit = softLimit;
|
|
mHardLimit = hardLimit;
|
|
EvictEntriesIfNecessary();
|
|
}
|
|
|
|
|
|
void
|
|
nsMemoryCacheDevice::EvictEntry(nsCacheEntry * entry)
|
|
{
|
|
// remove entry from our hashtable
|
|
mMemCacheEntries.RemoveEntry(entry);
|
|
|
|
// remove entry from the eviction list
|
|
PR_REMOVE_AND_INIT_LINK(entry);
|
|
|
|
// update statistics
|
|
PRUint32 memoryRecovered = entry->Size();
|
|
mTotalSize -= memoryRecovered;
|
|
mInactiveSize -= memoryRecovered;
|
|
--mEntryCount;
|
|
|
|
delete entry;
|
|
}
|
|
|
|
|
|
void
|
|
nsMemoryCacheDevice::EvictEntriesIfNecessary(void)
|
|
{
|
|
nsCacheEntry * entry, * next;
|
|
|
|
if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
|
|
return;
|
|
|
|
for (int i=mostLikelyToEvict; i<=leastLikelyToEvict; ++i) {
|
|
entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
|
|
while (entry != &mEvictionList[i]) {
|
|
if (entry->IsInUse()) {
|
|
entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
|
|
continue;
|
|
}
|
|
|
|
next = (nsCacheEntry *)PR_NEXT_LINK(entry);
|
|
EvictEntry(entry);
|
|
entry = next;
|
|
|
|
if ((mTotalSize < mHardLimit) && (mInactiveSize < mSoftLimit))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
nsMemoryCacheDevice::EvictionList(nsCacheEntry * entry, PRUint32 deltaSize)
|
|
{
|
|
PRUint32 size = entry->Size() + deltaSize;
|
|
if ((size > mEvictionThreshold) || (entry->ExpirationTime() != 0))
|
|
return mostLikelyToEvict;
|
|
|
|
return leastLikelyToEvict;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::Visit(nsICacheVisitor * visitor)
|
|
{
|
|
nsMemoryCacheDeviceInfo * deviceInfo = new nsMemoryCacheDeviceInfo(this);
|
|
nsCOMPtr<nsICacheDeviceInfo> deviceRef(deviceInfo);
|
|
if (!deviceInfo) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
PRBool keepGoing;
|
|
nsresult rv = visitor->VisitDevice(gMemoryDeviceID, deviceInfo, &keepGoing);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (!keepGoing)
|
|
return NS_OK;
|
|
|
|
nsCacheEntry * entry;
|
|
nsCOMPtr<nsICacheEntryInfo> entryRef;
|
|
|
|
for (int i=mostLikelyToEvict; i <= leastLikelyToEvict; ++i) {
|
|
entry = (nsCacheEntry *)PR_LIST_HEAD(&mEvictionList[i]);
|
|
while (entry != &mEvictionList[i]) {
|
|
nsCacheEntryInfo * entryInfo = new nsCacheEntryInfo(entry);
|
|
if (!entryInfo) return NS_ERROR_OUT_OF_MEMORY;
|
|
entryRef = entryInfo;
|
|
|
|
rv = visitor->VisitEntry(gMemoryDeviceID, entryInfo, &keepGoing);
|
|
entryInfo->DetachEntry();
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (!keepGoing) break;
|
|
|
|
entry = (nsCacheEntry *)PR_NEXT_LINK(entry);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMemoryCacheDevice::EvictEntries(const char * clientID)
|
|
{
|
|
nsCacheEntry * entry;
|
|
PRUint32 prefixLength = (clientID ? nsCRT::strlen(clientID) : 0);
|
|
|
|
for (int i=mostLikelyToEvict; i<=leastLikelyToEvict; ++i) {
|
|
PRCList * elem = PR_LIST_HEAD(&mEvictionList[i]);
|
|
while (elem != &mEvictionList[i]) {
|
|
entry = (nsCacheEntry *)elem;
|
|
elem = PR_NEXT_LINK(elem);
|
|
|
|
const char * key = entry->Key()->get();
|
|
if (clientID && nsCRT::strncmp(clientID, key, prefixLength) != 0)
|
|
continue;
|
|
|
|
if (entry->IsInUse()) {
|
|
nsresult rv = nsCacheService::GlobalInstance()->DoomEntry_Locked(entry);
|
|
if (NS_FAILED(rv)) return rv;
|
|
} else {
|
|
EvictEntry(entry);
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nsMemoryCacheDevice::SetCapacity(PRUint32 capacity)
|
|
{
|
|
PRUint32 hardLimit = capacity * 1024; // convert k into bytes
|
|
PRUint32 softLimit = hardLimit * 0.9;
|
|
AdjustMemoryLimits(softLimit, hardLimit);
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* nsMemoryCacheDeviceInfo - for implementing about:cache
|
|
*****************************************************************************/
|
|
|
|
|
|
NS_IMPL_ISUPPORTS1(nsMemoryCacheDeviceInfo, nsICacheDeviceInfo);
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsMemoryCacheDeviceInfo::GetDescription(char ** result)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
*result = nsCRT::strdup("Memory cache device");
|
|
if (!*result) return NS_ERROR_OUT_OF_MEMORY;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsMemoryCacheDeviceInfo::GetUsageReport(char ** result)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
*result = nsCRT::strdup("Memory cache usage report:");
|
|
if (!*result) return NS_ERROR_OUT_OF_MEMORY;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsMemoryCacheDeviceInfo::GetEntryCount(PRUint32 * result)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
// XXX compare calculated count vs. mEntryCount
|
|
*result = mDevice->mEntryCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsMemoryCacheDeviceInfo::GetTotalSize(PRUint32 * result)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
*result = mDevice->mTotalSize;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsMemoryCacheDeviceInfo::GetMaximumSize(PRUint32 * result)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(result);
|
|
*result = mDevice->mHardLimit;
|
|
return NS_OK;
|
|
}
|