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: #include "mozilla/MemoryReporting.h" michael@0: #include "nsCache.h" michael@0: #include michael@0: michael@0: #include "nscore.h" michael@0: #include "nsDiskCacheBinding.h" michael@0: #include "nsCacheService.h" michael@0: michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * static hash table callback functions michael@0: * michael@0: *****************************************************************************/ michael@0: struct HashTableEntry : PLDHashEntryHdr { michael@0: nsDiskCacheBinding * mBinding; michael@0: }; michael@0: michael@0: michael@0: static PLDHashNumber michael@0: HashKey( PLDHashTable *table, const void *key) michael@0: { michael@0: return (PLDHashNumber) NS_PTR_TO_INT32(key); michael@0: } michael@0: michael@0: michael@0: static bool michael@0: MatchEntry(PLDHashTable * /* table */, michael@0: const PLDHashEntryHdr * header, michael@0: const void * key) michael@0: { michael@0: HashTableEntry * hashEntry = (HashTableEntry *) header; michael@0: return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key)); michael@0: } michael@0: michael@0: static void michael@0: MoveEntry(PLDHashTable * /* table */, michael@0: const PLDHashEntryHdr * src, michael@0: PLDHashEntryHdr * dst) michael@0: { michael@0: ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding; michael@0: } michael@0: michael@0: michael@0: static void michael@0: ClearEntry(PLDHashTable * /* table */, michael@0: PLDHashEntryHdr * header) michael@0: { michael@0: ((HashTableEntry *)header)->mBinding = nullptr; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * Utility Functions michael@0: *****************************************************************************/ michael@0: nsDiskCacheBinding * michael@0: GetCacheEntryBinding(nsCacheEntry * entry) michael@0: { michael@0: return (nsDiskCacheBinding *) entry->Data(); michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheBinding michael@0: *****************************************************************************/ michael@0: michael@0: NS_IMPL_ISUPPORTS0(nsDiskCacheBinding) michael@0: michael@0: nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record) michael@0: : mCacheEntry(entry) michael@0: , mStreamIO(nullptr) michael@0: , mDeactivateEvent(nullptr) michael@0: { michael@0: NS_ASSERTION(record->ValidRecord(), "bad record"); michael@0: PR_INIT_CLIST(this); michael@0: mRecord = *record; michael@0: mDoomed = entry->IsDoomed(); michael@0: mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files michael@0: } michael@0: michael@0: nsDiskCacheBinding::~nsDiskCacheBinding() michael@0: { michael@0: // Grab the cache lock since the binding is stored in nsCacheEntry::mData michael@0: // and it is released using nsCacheService::ReleaseObject_Locked() which michael@0: // releases the object outside the cache lock. michael@0: nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEBINDING_DESTRUCTOR)); michael@0: michael@0: NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list"); michael@0: if (!PR_CLIST_IS_EMPTY(this)) michael@0: PR_REMOVE_LINK(this); // XXX why are we still on a list? michael@0: michael@0: // sever streamIO/binding link michael@0: if (mStreamIO) { michael@0: if (NS_FAILED(mStreamIO->ClearBinding())) michael@0: nsCacheService::DoomEntry(mCacheEntry); michael@0: NS_RELEASE(mStreamIO); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsDiskCacheBinding::EnsureStreamIO() michael@0: { michael@0: if (!mStreamIO) { michael@0: mStreamIO = new nsDiskCacheStreamIO(this); michael@0: if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(mStreamIO); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /****************************************************************************** michael@0: * nsDiskCacheBindery michael@0: * michael@0: * Keeps track of bound disk cache entries to detect for collisions. michael@0: * michael@0: *****************************************************************************/ michael@0: michael@0: const PLDHashTableOps nsDiskCacheBindery::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: nsDiskCacheBindery::nsDiskCacheBindery() michael@0: : initialized(false) michael@0: { michael@0: } michael@0: michael@0: michael@0: nsDiskCacheBindery::~nsDiskCacheBindery() michael@0: { michael@0: Reset(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsDiskCacheBindery::Init() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: PL_DHashTableInit(&table, &ops, nullptr, sizeof(HashTableEntry), 0); michael@0: initialized = true; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsDiskCacheBindery::Reset() 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: nsDiskCacheBinding * michael@0: nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry, michael@0: nsDiskCacheRecord * record) michael@0: { michael@0: NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); michael@0: nsCOMPtr data = entry->Data(); michael@0: if (data) { michael@0: NS_ERROR("cache entry already has bind data"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record); michael@0: if (!binding) return nullptr; michael@0: michael@0: // give ownership of the binding to the entry michael@0: entry->SetData(binding); michael@0: michael@0: // add binding to collision detection system michael@0: nsresult rv = AddBinding(binding); michael@0: if (NS_FAILED(rv)) { michael@0: entry->SetData(nullptr); michael@0: return nullptr; michael@0: } michael@0: michael@0: return binding; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * FindActiveEntry : to find active colliding entry so we can doom it michael@0: */ michael@0: nsDiskCacheBinding * michael@0: nsDiskCacheBindery::FindActiveBinding(uint32_t hashNumber) michael@0: { michael@0: NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); michael@0: // find hash entry for key michael@0: HashTableEntry * hashEntry; michael@0: hashEntry = michael@0: (HashTableEntry *) PL_DHashTableOperate(&table, michael@0: (void*)(uintptr_t) hashNumber, michael@0: PL_DHASH_LOOKUP); michael@0: if (PL_DHASH_ENTRY_IS_FREE(hashEntry)) return nullptr; michael@0: michael@0: // walk list looking for active entry michael@0: NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding"); michael@0: nsDiskCacheBinding * binding = hashEntry->mBinding; michael@0: while (binding->mCacheEntry->IsDoomed()) { michael@0: binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); michael@0: if (binding == hashEntry->mBinding) return nullptr; michael@0: } michael@0: return binding; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * AddBinding michael@0: * michael@0: * Called from FindEntry() if we read an entry off of disk michael@0: * - it may already have a generation number michael@0: * - a generation number conflict is an error michael@0: * michael@0: * Called from BindEntry() michael@0: * - a generation number needs to be assigned michael@0: */ michael@0: nsresult michael@0: nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(binding); michael@0: NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); michael@0: michael@0: // find hash entry for key michael@0: HashTableEntry * hashEntry; michael@0: hashEntry = (HashTableEntry *) michael@0: PL_DHashTableOperate(&table, michael@0: (void *)(uintptr_t) binding->mRecord.HashNumber(), michael@0: PL_DHASH_ADD); michael@0: if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (hashEntry->mBinding == nullptr) { michael@0: hashEntry->mBinding = binding; michael@0: if (binding->mGeneration == 0) michael@0: binding->mGeneration = 1; // if generation uninitialized, set it to 1 michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // insert binding in generation order michael@0: nsDiskCacheBinding * p = hashEntry->mBinding; michael@0: bool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation? michael@0: if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized michael@0: while (1) { michael@0: michael@0: if (binding->mGeneration < p->mGeneration) { michael@0: // here we are michael@0: PR_INSERT_BEFORE(binding, p); michael@0: if (hashEntry->mBinding == p) michael@0: hashEntry->mBinding = binding; michael@0: break; michael@0: } michael@0: michael@0: if (binding->mGeneration == p->mGeneration) { michael@0: if (calcGeneration) ++binding->mGeneration; // try the next generation michael@0: else { michael@0: NS_ERROR("### disk cache: generations collide!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: p = (nsDiskCacheBinding *)PR_NEXT_LINK(p); michael@0: if (p == hashEntry->mBinding) { michael@0: // end of line: insert here or die michael@0: p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation michael@0: if (p->mGeneration == 255) { michael@0: NS_WARNING("### disk cache: generation capacity at full"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: PR_INSERT_BEFORE(binding, hashEntry->mBinding); michael@0: break; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * RemoveBinding : remove binding from collision detection on deactivation michael@0: */ michael@0: void michael@0: nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding) michael@0: { michael@0: NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); michael@0: if (!initialized) return; michael@0: michael@0: HashTableEntry * hashEntry; michael@0: void * key = (void *)(uintptr_t)binding->mRecord.HashNumber(); michael@0: michael@0: hashEntry = (HashTableEntry*) PL_DHashTableOperate(&table, michael@0: (void*)(uintptr_t) key, michael@0: PL_DHASH_LOOKUP); michael@0: if (!PL_DHASH_ENTRY_IS_BUSY(hashEntry)) { michael@0: NS_WARNING("### disk cache: binding not in hashtable!"); michael@0: return; michael@0: } michael@0: michael@0: if (binding == hashEntry->mBinding) { michael@0: if (PR_CLIST_IS_EMPTY(binding)) { michael@0: // remove this hash entry michael@0: PL_DHashTableOperate(&table, michael@0: (void*)(uintptr_t) binding->mRecord.HashNumber(), michael@0: PL_DHASH_REMOVE); michael@0: return; michael@0: michael@0: } else { michael@0: // promote next binding to head, and unlink this binding michael@0: hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); michael@0: } michael@0: } michael@0: PR_REMOVE_AND_INIT_LINK(binding); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * ActiveBinding : PLDHashTable enumerate function to verify active bindings michael@0: */ michael@0: michael@0: PLDHashOperator michael@0: ActiveBinding(PLDHashTable * table, michael@0: PLDHashEntryHdr * hdr, michael@0: uint32_t number, michael@0: void * arg) michael@0: { michael@0: nsDiskCacheBinding * binding = ((HashTableEntry *)hdr)->mBinding; michael@0: NS_ASSERTION(binding, "### disk cache binding = nullptr!"); michael@0: michael@0: nsDiskCacheBinding * head = binding; michael@0: do { michael@0: if (binding->IsActive()) { michael@0: *((bool *)arg) = true; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); michael@0: } while (binding != head); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * ActiveBindings : return true if any bindings have open descriptors michael@0: */ michael@0: bool michael@0: nsDiskCacheBindery::ActiveBindings() michael@0: { michael@0: NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); michael@0: if (!initialized) return false; michael@0: michael@0: bool activeBinding = false; michael@0: PL_DHashTableEnumerate(&table, ActiveBinding, &activeBinding); michael@0: michael@0: return activeBinding; michael@0: } michael@0: michael@0: struct AccumulatorArg { michael@0: size_t mUsage; michael@0: mozilla::MallocSizeOf mMallocSizeOf; michael@0: }; michael@0: michael@0: PLDHashOperator michael@0: AccumulateHeapUsage(PLDHashTable *table, PLDHashEntryHdr *hdr, uint32_t number, michael@0: void *arg) michael@0: { michael@0: nsDiskCacheBinding *binding = ((HashTableEntry *)hdr)->mBinding; michael@0: NS_ASSERTION(binding, "### disk cache binding = nsnull!"); michael@0: michael@0: AccumulatorArg *acc = (AccumulatorArg *)arg; michael@0: michael@0: nsDiskCacheBinding *head = binding; michael@0: do { michael@0: acc->mUsage += acc->mMallocSizeOf(binding); michael@0: michael@0: if (binding->mStreamIO) { michael@0: acc->mUsage += binding->mStreamIO->SizeOfIncludingThis(acc->mMallocSizeOf); michael@0: } michael@0: michael@0: /* No good way to get at mDeactivateEvent internals for proper size, so michael@0: we use this as an estimate. */ michael@0: if (binding->mDeactivateEvent) { michael@0: acc->mUsage += acc->mMallocSizeOf(binding->mDeactivateEvent); michael@0: } michael@0: michael@0: binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding); michael@0: } while (binding != head); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /** michael@0: * SizeOfExcludingThis: return the amount of heap memory (bytes) being used by the bindery michael@0: */ michael@0: size_t michael@0: nsDiskCacheBindery::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) michael@0: { michael@0: NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized"); michael@0: if (!initialized) return 0; michael@0: michael@0: AccumulatorArg arg; michael@0: arg.mUsage = 0; michael@0: arg.mMallocSizeOf = aMallocSizeOf; michael@0: michael@0: PL_DHashTableEnumerate(&table, AccumulateHeapUsage, &arg); michael@0: michael@0: return arg.mUsage; michael@0: }