Files
Mozilla/mozilla/netwerk/cache/src/nsCacheService.cpp
gordon%netscape.com 0ea21fde31 [not part of build]
Restructured open cache entry code in preparation for async implementation, to better share code with synchronous version.

Changed nsCacheRequest, nsCacheEntry, nsCacheEntryDescriptor to inherit from PRCList rather than include mListLink member, and removed extraneous GetListNode/GetInstance methods.

Consolidated mAccessRequested, mStreamBased, and mStoragePolicy into a single PRUint32 in nsCacheRequest.  Added PRLock, PRCondVar, and a 'wait for validation' flag, used for synchronously opening cache entries.  Added accessor functions for these "attributes".

Record current event queue for asychronous requests to be used with GetProxyForObject().  Removed mRequestThread.


git-svn-id: svn://10.0.0.236/trunk@88535 18797224-902f-48f8-a5cc-f745e15eee43
2001-03-05 07:17:58 +00:00

617 lines
17 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 "nsCacheService.h"
#include "nsCacheEntry.h"
#include "nsCacheEntryDescriptor.h"
#include "nsCacheDevice.h"
#include "nsCacheRequest.h"
#include "nsMemoryCacheDevice.h"
#include "nsDiskCacheDevice.h"
#include "nsAutoLock.h"
#include "nsVoidArray.h"
#include "nsIEventQueueService.h"
#include "nsIEventQueue.h"
static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
nsCacheService * nsCacheService::gService = nsnull;
NS_IMPL_THREADSAFE_ISUPPORTS1(nsCacheService, nsICacheService)
nsCacheService::nsCacheService()
: mCacheServiceLock(nsnull),
mMemoryDevice(nsnull),
mDiskDevice(nsnull),
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;
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;
// create memory cache
mMemoryDevice = new nsMemoryCacheDevice;
if (!mMemoryDevice) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto error;
}
rv = mMemoryDevice->Init();
if (NS_FAILED(rv)) goto error;
// create disk cache
mDiskDevice = new nsDiskCacheDevice;
if (!mDiskDevice) {
rv = NS_ERROR_OUT_OF_MEMORY;
goto error;
}
rv = mDiskDevice->Init();
if (NS_FAILED(rv)) 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 check for pending requests...
// XXX finalize active entries
// deallocate memory and disk caches
delete mMemoryDevice;
mMemoryDevice = nsnull;
delete mDiskDevice;
mDiskDevice = nsnull;
PR_DestroyLock(mCacheServiceLock);
mCacheServiceLock = nsnull;
}
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;
nsCacheSession * session = new nsCacheSession(clientID, storagePolicy, streamBased);
if (!session) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*result = session);
return NS_OK;
}
/* void visitEntries (in nsICacheVisitor visitor); */
NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
nsCacheService::CreateRequest(nsCacheSession * session,
const char * clientKey,
nsCacheAccessMode accessRequested,
nsICacheListener * listener,
nsCacheRequest ** request)
{
NS_ASSERTION(request, "CommonOpenCacheEntry: request or entry is null");
nsCString * key = new nsCString(*session->ClientID() +
nsLiteralCString(":") +
nsLiteralCString(clientKey));
if (!key)
return NS_ERROR_OUT_OF_MEMORY;
// create request
*request = new nsCacheRequest(key, listener, accessRequested,
session->StreamBased(),
session->StoragePolicy());
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
nsresult rv;
// XXX can we just keep a reference so we don't have to do this everytime?
NS_WITH_SERVICE(nsIEventQueueService, eventQService, kEventQueueServiceCID, &rv);
if (NS_FAILED(rv)) goto error;
rv = eventQService->ResolveEventQueue(NS_CURRENT_EVENTQ,
getter_AddRefs((*request)->mEventQ));
if (NS_FAILED(rv)) goto error;
if (!(*request)->mEventQ) {
rv = NS_ERROR_UNEXPECTED; // XXX what is the right error?
goto error;
}
return NS_OK;
error:
delete *request;
*request = nsnull;
return rv;
}
nsresult
nsCacheService::OpenCacheEntry(nsCacheSession * session,
const char * key,
nsCacheAccessMode accessRequested,
nsICacheListener * listener,
nsICacheEntryDescriptor ** result)
{
if (*result)
*result = nsnull;
nsCacheRequest * request = nsnull;
nsCacheEntry * entry = nsnull;
nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
nsresult rv = CreateRequest(session, key, accessRequested, listener, &request);
if (NS_FAILED(rv)) return rv;
nsAutoLock cacheService(mCacheServiceLock);
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!");
rv = entry->RequestAccess(request, &accessGranted);
if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
if (listener) // async exits - validate, doom, or close will resume
return rv;
cacheService.unlock();
rv = request->WaitForValidation();
cacheService.lock();
if (NS_FAILED(rv)) break;
// okay, we're ready to process this request, request access again
}
if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
}
nsICacheEntryDescriptor * descriptor;
rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
if (listener) {
// XXX call listener to report error or descriptor
} else {
*result = descriptor;
}
delete request;
return rv;
}
#if 0
nsresult
nsCacheService::OpenCacheEntry(nsCacheSession * session,
const char * key,
nsCacheAccessMode accessRequested,
nsICacheEntryDescriptor ** result)
{
*result = nsnull;
if (!mCacheServiceLock) return NS_ERROR_NOT_INITIALIZED;
nsCacheRequest * request = nsnull;
nsCacheEntry * entry = nsnull;
nsresult rv = CreateRequest(session, key, accessRequested, nsnull, &request);
if (NS_FAILED(rv)) return rv;
while (1) {
// XXX acquire lock
rv = ActivateEntry(request, &entry);
if (NS_FAILED(rv)) break;
// XXX check for NS_ERROR_CACHE_KEY_NOT_FOUND & READ-ONLY request
rv = entry->Open(request, result); // XXX release lock before waiting on request
if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
}
delete request;
return rv;
}
nsresult
nsCacheService::AsyncOpenCacheEntry(nsCacheSession * session,
const char * key,
nsCacheAccessMode accessRequested,
nsICacheListener * listener)
{
if (!mCacheServiceLock) return NS_ERROR_NOT_INITIALIZED;
if (!listener) return NS_ERROR_NULL_POINTER;
nsCacheRequest * request = nsnull;
nsCacheEntry * entry = nsnull;
nsresult rv = CreateRequest(session, key, accessRequested, listener, &request);
// XXX acquire service lock PR_Lock(mCacheServiceLock);
rv = ActivateEntry(request, &entry);
if (NS_SUCCEEDED(rv)) {
entry->AsyncOpen(request); // XXX release lock after request queued, etc.
}
return rv;
}
#endif
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 && !(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 < ConvertPRTimeToSeconds(PR_Now()))))
// XXX beginning to look a lot like lisp
{
// this is FORCE-WRITE request or the entry has expired
rv = DoomEntry_Internal(entry);
if (NS_FAILED(rv)) {
// XXX what to do? Increment FailedDooms counter?
}
if (entry->IsNotInUse()) {
DeactivateEntry(entry); // tell device to get rid of it
}
entry = nsnull;
}
if (!entry) {
entry = new nsCacheEntry(request->mKey,
request->IsStreamBased(),
request->StoragePolicy());
if (!entry)
return NS_ERROR_OUT_OF_MEMORY;
// XXX we could perform an early bind in some cases based on storage policy
}
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
}
return rv;
}
nsCacheEntry *
nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy)
{
nsCacheEntry * entry = nsnull;
if ((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_IN_MEMORY)) {
entry = mMemoryDevice->FindEntry(key);
}
if (!entry &&
((policy == nsICache::STORE_ANYWHERE) || (policy == nsICache::STORE_ON_DISK))) {
entry = mDiskDevice->FindEntry(key);
}
return entry;
}
nsresult
nsCacheService::BindEntry(nsCacheEntry * entry)
{
nsresult rv = NS_OK;
nsCacheDevice * device;
if (entry->IsStreamData() && entry->IsAllowedOnDisk()) {
// this is the default
device = mDiskDevice;
} else {
NS_ASSERTION(entry->IsAllowedInMemory(), "oops.. bad flags");
device = mMemoryDevice;
}
rv = device->BindEntry(entry);
if (NS_SUCCEEDED(rv))
entry->SetCacheDevice(device);
return rv;
}
nsresult
nsCacheService::ValidateEntry(nsCacheEntry * entry)
{
// XXX bind if not bound
// XXX convert pending requests to descriptors, etc.
entry->MarkValid();
return NS_OK;
}
nsresult
nsCacheService::DoomEntry(nsCacheEntry * entry)
{
nsAutoLock lock(mCacheServiceLock);
return DoomEntry_Internal(entry);
}
nsresult
nsCacheService::DoomEntry_Internal(nsCacheEntry * entry)
{
if (entry->IsDoomed()) return NS_OK;
nsresult rv = NS_OK;
entry->MarkDoomed();
nsCacheDevice * device = entry->CacheDevice();
if (device) {
rv = device->DoomEntry(entry);
// XXX check rv, but what can we really do...
}
if (entry->IsActive()) {
// remove from active entries
rv = mActiveEntries.RemoveEntry(entry);
entry->MarkInactive();
if (NS_FAILED(rv)) {
// XXX what to do
}
}
// 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);
// XXX reprocess pending requests
return rv;
}
nsresult
nsCacheService::OnDataSizeChange(nsCacheEntry * entry, PRInt32 deltaSize)
{
nsAutoLock lock(mCacheServiceLock);
nsresult rv = NS_OK;
nsCacheDevice * device = entry->CacheDevice();
if (!device) {
rv = BindEntry(entry);
if (NS_FAILED(rv)) return rv;
device = entry->CacheDevice();
}
return device->OnDataSizeChange(entry, deltaSize);
}
nsresult
nsCacheService::GetTransportForEntry(nsCacheEntry * entry,
nsCacheAccessMode mode,
nsITransport ** result)
{
nsAutoLock lock(mCacheServiceLock);
nsresult rv = NS_OK;
nsCacheDevice * device = entry->CacheDevice();
if (!device) {
rv = BindEntry(entry);
if (NS_FAILED(rv)) return rv;
device = entry->CacheDevice();
}
return device->GetTransportForEntry(entry, mode, result);
}
void
nsCacheService::CloseDescriptor(nsCacheEntryDescriptor * descriptor)
{
nsAutoLock lock(mCacheServiceLock);
// ask entry to remove descriptor
nsCacheEntry * entry = descriptor->CacheEntry();
PRBool stillActive = entry->RemoveDescriptor(descriptor);
// XXX if (!entry->IsValid()) process pending requests
if (!stillActive) {
DeactivateEntry(entry);
}
}
void
nsCacheService::DeactivateEntry(nsCacheEntry * entry)
{
nsresult rv = NS_OK;
NS_ASSERTION(entry->IsNotInUse(), "deactivating an entry while in use!");
if (entry->IsDoomed()) {
// remove from Doomed list
PR_REMOVE_AND_INIT_LINK(entry);
} else {
if (entry->IsActive()) {
// remove from active entries
rv = mActiveEntries.RemoveEntry(entry);
entry->MarkInactive();
NS_ASSERTION(NS_SUCCEEDED(rv),"failed to remove an active entry !?!");
}
}
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
}
}
/**
* Cache Service Utility Functions
*/
// time conversion utils from nsCachedNetData.cpp
// Convert PRTime to unix-style time_t, i.e. seconds since the epoch
PRUint32
ConvertPRTimeToSeconds(PRTime time64)
{
double fpTime;
LL_L2D(fpTime, time64);
return (PRUint32)(fpTime * 1e-6 + 0.5);
}
// Convert unix-style time_t, i.e. seconds since the epoch, to PRTime
PRTime
ConvertSecondsToPRTime(PRUint32 seconds)
{
PRInt64 t64;
LL_I2L(t64, seconds);
PRInt64 mil;
LL_I2L(mil, 1000000);
LL_MUL(t64, t64, mil);
return t64;
}