michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: #include "nsCache.h" michael@0: #include "nspr.h" michael@0: #include "nsCacheEntry.h" michael@0: #include "nsCacheEntryDescriptor.h" michael@0: #include "nsCacheMetaData.h" michael@0: #include "nsCacheRequest.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsICacheService.h" michael@0: #include "nsCacheService.h" michael@0: #include "nsCacheDevice.h" michael@0: #include "nsHashKeys.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: nsCacheEntry::nsCacheEntry(const nsACString & key, michael@0: bool streamBased, michael@0: nsCacheStoragePolicy storagePolicy) michael@0: : mKey(key), michael@0: mFetchCount(0), michael@0: mLastFetched(0), michael@0: mLastModified(0), michael@0: mExpirationTime(nsICache::NO_EXPIRATION_TIME), michael@0: mFlags(0), michael@0: mPredictedDataSize(-1), michael@0: mDataSize(0), michael@0: mCacheDevice(nullptr), michael@0: mCustomDevice(nullptr), michael@0: mData(nullptr) michael@0: { michael@0: MOZ_COUNT_CTOR(nsCacheEntry); michael@0: PR_INIT_CLIST(this); michael@0: PR_INIT_CLIST(&mRequestQ); michael@0: PR_INIT_CLIST(&mDescriptorQ); michael@0: michael@0: if (streamBased) MarkStreamBased(); michael@0: SetStoragePolicy(storagePolicy); michael@0: michael@0: MarkPublic(); michael@0: michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(this, key.BeginReading()); michael@0: } michael@0: michael@0: michael@0: nsCacheEntry::~nsCacheEntry() michael@0: { michael@0: MOZ_COUNT_DTOR(nsCacheEntry); michael@0: michael@0: if (mData) michael@0: nsCacheService::ReleaseObject_Locked(mData, mThread); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsCacheEntry::Create( const char * key, michael@0: bool streamBased, michael@0: nsCacheStoragePolicy storagePolicy, michael@0: nsCacheDevice * device, michael@0: nsCacheEntry ** result) michael@0: { michael@0: nsCacheEntry* entry = new nsCacheEntry(nsCString(key), michael@0: streamBased, michael@0: storagePolicy); michael@0: entry->SetCacheDevice(device); michael@0: *result = entry; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntry::Fetched() michael@0: { michael@0: mLastFetched = SecondsFromPRTime(PR_Now()); michael@0: ++mFetchCount; michael@0: MarkEntryDirty(); michael@0: } michael@0: michael@0: michael@0: const char * michael@0: nsCacheEntry::GetDeviceID() michael@0: { michael@0: if (mCacheDevice) return mCacheDevice->GetDeviceID(); michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntry::TouchData() michael@0: { michael@0: mLastModified = SecondsFromPRTime(PR_Now()); michael@0: MarkDataDirty(); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntry::SetData(nsISupports * data) michael@0: { michael@0: if (mData) { michael@0: nsCacheService::ReleaseObject_Locked(mData, mThread); michael@0: mData = nullptr; michael@0: } michael@0: michael@0: if (data) { michael@0: NS_ADDREF(mData = data); michael@0: mThread = do_GetCurrentThread(); michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntry::TouchMetaData() michael@0: { michael@0: mLastModified = SecondsFromPRTime(PR_Now()); michael@0: MarkMetaDataDirty(); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * cache entry states michael@0: * 0 descriptors (new entry) michael@0: * 0 descriptors (existing, bound entry) michael@0: * n descriptors (existing, bound entry) valid michael@0: * n descriptors (existing, bound entry) not valid (wait until valid or doomed) michael@0: */ michael@0: michael@0: nsresult michael@0: nsCacheEntry::RequestAccess(nsCacheRequest * request, nsCacheAccessMode *accessGranted) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (IsDoomed()) return NS_ERROR_CACHE_ENTRY_DOOMED; michael@0: michael@0: if (!IsInitialized()) { michael@0: // brand new, unbound entry michael@0: if (request->IsStreamBased()) MarkStreamBased(); michael@0: MarkInitialized(); michael@0: michael@0: *accessGranted = request->AccessRequested() & nsICache::ACCESS_WRITE; michael@0: NS_ASSERTION(*accessGranted, "new cache entry for READ-ONLY request"); michael@0: PR_APPEND_LINK(request, &mRequestQ); michael@0: return rv; michael@0: } michael@0: michael@0: if (IsStreamData() != request->IsStreamBased()) { michael@0: *accessGranted = nsICache::ACCESS_NONE; michael@0: return request->IsStreamBased() ? michael@0: NS_ERROR_CACHE_DATA_IS_NOT_STREAM : NS_ERROR_CACHE_DATA_IS_STREAM; michael@0: } michael@0: michael@0: if (PR_CLIST_IS_EMPTY(&mDescriptorQ)) { michael@0: // 1st descriptor for existing bound entry michael@0: *accessGranted = request->AccessRequested(); michael@0: if (*accessGranted & nsICache::ACCESS_WRITE) { michael@0: MarkInvalid(); michael@0: } else { michael@0: MarkValid(); michael@0: } michael@0: } else { michael@0: // nth request for existing, bound entry michael@0: *accessGranted = request->AccessRequested() & ~nsICache::ACCESS_WRITE; michael@0: if (!IsValid()) michael@0: rv = NS_ERROR_CACHE_WAIT_FOR_VALIDATION; michael@0: } michael@0: PR_APPEND_LINK(request,&mRequestQ); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsCacheEntry::CreateDescriptor(nsCacheRequest * request, michael@0: nsCacheAccessMode accessGranted, michael@0: nsICacheEntryDescriptor ** result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(request && result); michael@0: michael@0: nsCacheEntryDescriptor * descriptor = michael@0: new nsCacheEntryDescriptor(this, accessGranted); michael@0: michael@0: // XXX check request is on q michael@0: PR_REMOVE_AND_INIT_LINK(request); // remove request regardless of success michael@0: michael@0: if (descriptor == nullptr) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: PR_APPEND_LINK(descriptor, &mDescriptorQ); michael@0: michael@0: CACHE_LOG_DEBUG((" descriptor %p created for request %p on entry %p\n", michael@0: descriptor, request, this)); michael@0: michael@0: NS_ADDREF(*result = descriptor); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsCacheEntry::RemoveRequest(nsCacheRequest * request) michael@0: { michael@0: // XXX if debug: verify this request belongs to this entry michael@0: PR_REMOVE_AND_INIT_LINK(request); michael@0: michael@0: // return true if this entry should stay active michael@0: return !((PR_CLIST_IS_EMPTY(&mRequestQ)) && michael@0: (PR_CLIST_IS_EMPTY(&mDescriptorQ))); michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor * descriptor, michael@0: bool * doomEntry) michael@0: { michael@0: NS_ASSERTION(descriptor->CacheEntry() == this, "### Wrong cache entry!!"); michael@0: michael@0: *doomEntry = descriptor->ClearCacheEntry(); michael@0: michael@0: PR_REMOVE_AND_INIT_LINK(descriptor); michael@0: michael@0: if (!PR_CLIST_IS_EMPTY(&mDescriptorQ)) michael@0: return true; // stay active if we still have open descriptors michael@0: michael@0: if (PR_CLIST_IS_EMPTY(&mRequestQ)) michael@0: return false; // no descriptors or requests, we can deactivate michael@0: michael@0: return true; // find next best request to give a descriptor to michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntry::DetachDescriptors() michael@0: { michael@0: nsCacheEntryDescriptor * descriptor = michael@0: (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ); michael@0: michael@0: while (descriptor != &mDescriptorQ) { michael@0: nsCacheEntryDescriptor * nextDescriptor = michael@0: (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor); michael@0: michael@0: descriptor->ClearCacheEntry(); michael@0: PR_REMOVE_AND_INIT_LINK(descriptor); michael@0: descriptor = nextDescriptor; michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntry::GetDescriptors( michael@0: nsTArray > &outDescriptors) michael@0: { michael@0: nsCacheEntryDescriptor * descriptor = michael@0: (nsCacheEntryDescriptor *)PR_LIST_HEAD(&mDescriptorQ); michael@0: michael@0: while (descriptor != &mDescriptorQ) { michael@0: nsCacheEntryDescriptor * nextDescriptor = michael@0: (nsCacheEntryDescriptor *)PR_NEXT_LINK(descriptor); michael@0: michael@0: outDescriptors.AppendElement(descriptor); michael@0: descriptor = nextDescriptor; michael@0: } michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsCacheEntryInfo - for implementing about:cache michael@0: *****************************************************************************/ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsCacheEntryInfo, nsICacheEntryInfo) michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetClientID(char ** clientID) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(clientID); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return ClientIDFromCacheKey(*mCacheEntry->Key(), clientID); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetDeviceID(char ** deviceID) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(deviceID); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *deviceID = NS_strdup(mCacheEntry->GetDeviceID()); michael@0: return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetKey(nsACString &key) michael@0: { michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return ClientKeyFromCacheKey(*mCacheEntry->Key(), key); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetFetchCount(int32_t * fetchCount) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(fetchCount); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *fetchCount = mCacheEntry->FetchCount(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetLastFetched(uint32_t * lastFetched) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(lastFetched); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *lastFetched = mCacheEntry->LastFetched(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetLastModified(uint32_t * lastModified) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(lastModified); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *lastModified = mCacheEntry->LastModified(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetExpirationTime(uint32_t * expirationTime) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(expirationTime); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *expirationTime = mCacheEntry->ExpirationTime(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::GetDataSize(uint32_t * dataSize) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(dataSize); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *dataSize = mCacheEntry->DataSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsCacheEntryInfo::IsStreamBased(bool * result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(result); michael@0: if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *result = mCacheEntry->IsStreamData(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsCacheEntryHashTable michael@0: *****************************************************************************/ michael@0: michael@0: const PLDHashTableOps michael@0: nsCacheEntryHashTable::ops = michael@0: { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: HashKey, michael@0: MatchEntry, michael@0: MoveEntry, michael@0: ClearEntry, michael@0: PL_DHashFinalizeStub michael@0: }; michael@0: michael@0: michael@0: nsCacheEntryHashTable::nsCacheEntryHashTable() michael@0: : initialized(false) michael@0: { michael@0: MOZ_COUNT_CTOR(nsCacheEntryHashTable); michael@0: } michael@0: michael@0: michael@0: nsCacheEntryHashTable::~nsCacheEntryHashTable() michael@0: { michael@0: MOZ_COUNT_DTOR(nsCacheEntryHashTable); michael@0: if (initialized) michael@0: Shutdown(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsCacheEntryHashTable::Init() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: initialized = PL_DHashTableInit(&table, &ops, nullptr, michael@0: sizeof(nsCacheEntryHashTableEntry), michael@0: 512, fallible_t()); michael@0: michael@0: if (!initialized) rv = NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsCacheEntryHashTable::Shutdown() michael@0: { michael@0: if (initialized) { michael@0: PL_DHashTableFinish(&table); michael@0: initialized = false; michael@0: } michael@0: } michael@0: michael@0: michael@0: nsCacheEntry * michael@0: nsCacheEntryHashTable::GetEntry( const nsCString * key) michael@0: { michael@0: PLDHashEntryHdr *hashEntry; michael@0: nsCacheEntry *result = nullptr; michael@0: michael@0: NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); michael@0: if (!initialized) return nullptr; michael@0: michael@0: hashEntry = PL_DHashTableOperate(&table, key, PL_DHASH_LOOKUP); michael@0: if (PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { michael@0: result = ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsCacheEntryHashTable::AddEntry( nsCacheEntry *cacheEntry) michael@0: { michael@0: PLDHashEntryHdr *hashEntry; michael@0: michael@0: NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); michael@0: if (!initialized) return NS_ERROR_NOT_INITIALIZED; michael@0: if (!cacheEntry) return NS_ERROR_NULL_POINTER; michael@0: michael@0: hashEntry = PL_DHashTableOperate(&table, &(cacheEntry->mKey), PL_DHASH_ADD); michael@0: #ifndef DEBUG_dougt michael@0: NS_ASSERTION(((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry == 0, michael@0: "### nsCacheEntryHashTable::AddEntry - entry already used"); michael@0: #endif michael@0: ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = cacheEntry; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntryHashTable::RemoveEntry( nsCacheEntry *cacheEntry) michael@0: { michael@0: NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); michael@0: NS_ASSERTION(cacheEntry, "### cacheEntry == nullptr"); michael@0: michael@0: if (!initialized) return; // NS_ERROR_NOT_INITIALIZED michael@0: michael@0: #if DEBUG michael@0: // XXX debug code to make sure we have the entry we're trying to remove michael@0: nsCacheEntry *check = GetEntry(&(cacheEntry->mKey)); michael@0: NS_ASSERTION(check == cacheEntry, "### Attempting to remove unknown cache entry!!!"); michael@0: #endif michael@0: (void) PL_DHashTableOperate(&table, &(cacheEntry->mKey), PL_DHASH_REMOVE); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntryHashTable::VisitEntries( PLDHashEnumerator etor, void *arg) michael@0: { michael@0: NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized"); michael@0: if (!initialized) return; // NS_ERROR_NOT_INITIALIZED michael@0: PL_DHashTableEnumerate(&table, etor, arg); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * hash table operation callback functions michael@0: */ michael@0: michael@0: PLDHashNumber michael@0: nsCacheEntryHashTable::HashKey( PLDHashTable *table, const void *key) michael@0: { michael@0: return HashString(*static_cast(key)); michael@0: } michael@0: michael@0: bool michael@0: nsCacheEntryHashTable::MatchEntry(PLDHashTable * /* table */, michael@0: const PLDHashEntryHdr * hashEntry, michael@0: const void * key) michael@0: { michael@0: NS_ASSERTION(key != nullptr, "### nsCacheEntryHashTable::MatchEntry : null key"); michael@0: nsCacheEntry *cacheEntry = ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry; michael@0: michael@0: return cacheEntry->mKey.Equals(*(nsCString *)key); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntryHashTable::MoveEntry(PLDHashTable * /* table */, michael@0: const PLDHashEntryHdr *from, michael@0: PLDHashEntryHdr *to) michael@0: { michael@0: ((nsCacheEntryHashTableEntry *)to)->cacheEntry = michael@0: ((nsCacheEntryHashTableEntry *)from)->cacheEntry; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsCacheEntryHashTable::ClearEntry(PLDHashTable * /* table */, michael@0: PLDHashEntryHdr * hashEntry) michael@0: { michael@0: ((nsCacheEntryHashTableEntry *)hashEntry)->cacheEntry = 0; michael@0: }