gecko/netwerk/cache/nsDiskCacheBinding.cpp
Nicholas Nethercote b945b71cfa Bug 1174625 - Overhaul PLDHashTable's iterator. r=froydnj.
This change splits PLDHashTable::Iterator::NextEntry() into two separate
functions, which allow you to get the current element and advance the iterator
separately, which means you can use a for-loop to iterate instead of a
while-loop.

As part of this change, the internals of PLDHashTable::Iterator were
significantly changed and simplified (and modelled after js::HashTable's
equivalent code). It's no longer duplicating code from PL_DHashTableEnumerator.
The chaos mode code was a casualty of this, but given how unreliable that code
has proven to be (see bug 1173212, bug 1174046) this is for the best. (We can
reimplement chaos mode once PLDHashTable::Iterator is back on more solid
footing again, if we think it's important.)

All these changes will make it much easier to add an alternative Iterator that
removes elements, which was turning out to be difficult with the prior code.

In order to make the for-loop header usually fit on a single line, I
deliberately renamed a bunch of things to have shorter names.

In summary, you used to write this:

  PLDHashTable::Iterator iter(&table);
  while (iter.HasMoreEntries()) {
    auto entry = static_cast<FooEntry*>(iter.NextEntry());
    // ... do stuff with |entry| ...
  }
  // iter's scope extends beyond here

and now you write this:

  for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
    auto entry = static_cast<FooEntry*>(iter.Get());
    // ... do stuff with |entry| ...
  }
  // iter's scope doesn't reach here
2015-06-11 21:19:53 -07:00

380 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/MemoryReporting.h"
#include "nsCache.h"
#include <limits.h>
#include "nscore.h"
#include "nsDiskCacheBinding.h"
#include "nsCacheService.h"
/******************************************************************************
* static hash table callback functions
*
*****************************************************************************/
struct HashTableEntry : PLDHashEntryHdr {
nsDiskCacheBinding * mBinding;
};
static PLDHashNumber
HashKey( PLDHashTable *table, const void *key)
{
return (PLDHashNumber) NS_PTR_TO_INT32(key);
}
static bool
MatchEntry(PLDHashTable * /* table */,
const PLDHashEntryHdr * header,
const void * key)
{
HashTableEntry * hashEntry = (HashTableEntry *) header;
return (hashEntry->mBinding->mRecord.HashNumber() == (PLDHashNumber) NS_PTR_TO_INT32(key));
}
static void
MoveEntry(PLDHashTable * /* table */,
const PLDHashEntryHdr * src,
PLDHashEntryHdr * dst)
{
((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding;
}
static void
ClearEntry(PLDHashTable * /* table */,
PLDHashEntryHdr * header)
{
((HashTableEntry *)header)->mBinding = nullptr;
}
/******************************************************************************
* Utility Functions
*****************************************************************************/
nsDiskCacheBinding *
GetCacheEntryBinding(nsCacheEntry * entry)
{
return (nsDiskCacheBinding *) entry->Data();
}
/******************************************************************************
* nsDiskCacheBinding
*****************************************************************************/
NS_IMPL_ISUPPORTS0(nsDiskCacheBinding)
nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry* entry, nsDiskCacheRecord * record)
: mCacheEntry(entry)
, mStreamIO(nullptr)
, mDeactivateEvent(nullptr)
{
NS_ASSERTION(record->ValidRecord(), "bad record");
PR_INIT_CLIST(this);
mRecord = *record;
mDoomed = entry->IsDoomed();
mGeneration = record->Generation(); // 0 == uninitialized, or data & meta using block files
}
nsDiskCacheBinding::~nsDiskCacheBinding()
{
// Grab the cache lock since the binding is stored in nsCacheEntry::mData
// and it is released using nsCacheService::ReleaseObject_Locked() which
// releases the object outside the cache lock.
nsCacheServiceAutoLock lock(LOCK_TELEM(NSDISKCACHEBINDING_DESTRUCTOR));
NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list");
if (!PR_CLIST_IS_EMPTY(this))
PR_REMOVE_LINK(this); // XXX why are we still on a list?
// sever streamIO/binding link
if (mStreamIO) {
if (NS_FAILED(mStreamIO->ClearBinding()))
nsCacheService::DoomEntry(mCacheEntry);
NS_RELEASE(mStreamIO);
}
}
nsresult
nsDiskCacheBinding::EnsureStreamIO()
{
if (!mStreamIO) {
mStreamIO = new nsDiskCacheStreamIO(this);
if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(mStreamIO);
}
return NS_OK;
}
/******************************************************************************
* nsDiskCacheBindery
*
* Keeps track of bound disk cache entries to detect for collisions.
*
*****************************************************************************/
const PLDHashTableOps nsDiskCacheBindery::ops =
{
HashKey,
MatchEntry,
MoveEntry,
ClearEntry
};
nsDiskCacheBindery::nsDiskCacheBindery()
: table(&ops, sizeof(HashTableEntry), kInitialTableLength)
, initialized(false)
{
}
nsDiskCacheBindery::~nsDiskCacheBindery()
{
Reset();
}
void
nsDiskCacheBindery::Init()
{
table.ClearAndPrepareForLength(kInitialTableLength);
initialized = true;
}
void
nsDiskCacheBindery::Reset()
{
if (initialized) {
table.ClearAndPrepareForLength(kInitialTableLength);
initialized = false;
}
}
nsDiskCacheBinding *
nsDiskCacheBindery::CreateBinding(nsCacheEntry * entry,
nsDiskCacheRecord * record)
{
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
nsCOMPtr<nsISupports> data = entry->Data();
if (data) {
NS_ERROR("cache entry already has bind data");
return nullptr;
}
nsDiskCacheBinding * binding = new nsDiskCacheBinding(entry, record);
if (!binding) return nullptr;
// give ownership of the binding to the entry
entry->SetData(binding);
// add binding to collision detection system
nsresult rv = AddBinding(binding);
if (NS_FAILED(rv)) {
entry->SetData(nullptr);
return nullptr;
}
return binding;
}
/**
* FindActiveEntry : to find active colliding entry so we can doom it
*/
nsDiskCacheBinding *
nsDiskCacheBindery::FindActiveBinding(uint32_t hashNumber)
{
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
// find hash entry for key
HashTableEntry * hashEntry;
hashEntry =
(HashTableEntry *) PL_DHashTableSearch(&table,
(void*)(uintptr_t) hashNumber);
if (!hashEntry) return nullptr;
// walk list looking for active entry
NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding");
nsDiskCacheBinding * binding = hashEntry->mBinding;
while (binding->mCacheEntry->IsDoomed()) {
binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
if (binding == hashEntry->mBinding) return nullptr;
}
return binding;
}
/**
* AddBinding
*
* Called from FindEntry() if we read an entry off of disk
* - it may already have a generation number
* - a generation number conflict is an error
*
* Called from BindEntry()
* - a generation number needs to be assigned
*/
nsresult
nsDiskCacheBindery::AddBinding(nsDiskCacheBinding * binding)
{
NS_ENSURE_ARG_POINTER(binding);
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
// find hash entry for key
HashTableEntry * hashEntry;
hashEntry = (HashTableEntry *)
PL_DHashTableAdd(&table,
(void *)(uintptr_t) binding->mRecord.HashNumber(),
fallible);
if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY;
if (hashEntry->mBinding == nullptr) {
hashEntry->mBinding = binding;
if (binding->mGeneration == 0)
binding->mGeneration = 1; // if generation uninitialized, set it to 1
return NS_OK;
}
// insert binding in generation order
nsDiskCacheBinding * p = hashEntry->mBinding;
bool calcGeneration = (binding->mGeneration == 0); // do we need to calculate generation?
if (calcGeneration) binding->mGeneration = 1; // initialize to 1 if uninitialized
while (1) {
if (binding->mGeneration < p->mGeneration) {
// here we are
PR_INSERT_BEFORE(binding, p);
if (hashEntry->mBinding == p)
hashEntry->mBinding = binding;
break;
}
if (binding->mGeneration == p->mGeneration) {
if (calcGeneration) ++binding->mGeneration; // try the next generation
else {
NS_ERROR("### disk cache: generations collide!");
return NS_ERROR_UNEXPECTED;
}
}
p = (nsDiskCacheBinding *)PR_NEXT_LINK(p);
if (p == hashEntry->mBinding) {
// end of line: insert here or die
p = (nsDiskCacheBinding *)PR_PREV_LINK(p); // back up and check generation
if (p->mGeneration == 255) {
NS_WARNING("### disk cache: generation capacity at full");
return NS_ERROR_UNEXPECTED;
}
PR_INSERT_BEFORE(binding, hashEntry->mBinding);
break;
}
}
return NS_OK;
}
/**
* RemoveBinding : remove binding from collision detection on deactivation
*/
void
nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding * binding)
{
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
if (!initialized) return;
HashTableEntry * hashEntry;
void * key = (void *)(uintptr_t)binding->mRecord.HashNumber();
hashEntry = (HashTableEntry*) PL_DHashTableSearch(&table,
(void*)(uintptr_t) key);
if (!hashEntry) {
NS_WARNING("### disk cache: binding not in hashtable!");
return;
}
if (binding == hashEntry->mBinding) {
if (PR_CLIST_IS_EMPTY(binding)) {
// remove this hash entry
PL_DHashTableRemove(&table,
(void*)(uintptr_t) binding->mRecord.HashNumber());
return;
} else {
// promote next binding to head, and unlink this binding
hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
}
}
PR_REMOVE_AND_INIT_LINK(binding);
}
/**
* ActiveBindings: return true if any bindings have open descriptors.
*/
bool
nsDiskCacheBindery::ActiveBindings()
{
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
if (!initialized) return false;
for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
auto entry = static_cast<HashTableEntry*>(iter.Get());
nsDiskCacheBinding* binding = entry->mBinding;
nsDiskCacheBinding* head = binding;
do {
if (binding->IsActive()) {
return true;
}
binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
} while (binding != head);
}
return false;
}
/**
* SizeOfExcludingThis: return the amount of heap memory (bytes) being used by
* the bindery.
*/
size_t
nsDiskCacheBindery::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
{
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
if (!initialized) return 0;
size_t size = 0;
for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
auto entry = static_cast<HashTableEntry*>(iter.Get());
nsDiskCacheBinding* binding = entry->mBinding;
nsDiskCacheBinding* head = binding;
do {
size += aMallocSizeOf(binding);
if (binding->mStreamIO) {
size += binding->mStreamIO->SizeOfIncludingThis(aMallocSizeOf);
}
// No good way to get at mDeactivateEvent internals for proper
// size, so we use this as an estimate.
if (binding->mDeactivateEvent) {
size += aMallocSizeOf(binding->mDeactivateEvent);
}
binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
} while (binding != head);
}
return size;
}