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
617 lines
17 KiB
C++
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;
|
|
}
|