gecko-dev/netwerk/cache/nsDiskCacheBinding.cpp
Nathan Froyd e7b3b3140d Bug 1415980 - make hash keys movable and not copyable; r=erahm
Everything that goes in a PLDHashtable (and its derivatives, like
nsTHashtable) needs to inherit from PLDHashEntryHdr. But through a lack
of enforcement, copy constructors for these derived classes didn't
explicitly invoke the copy constructor for PLDHashEntryHdr (and the
compiler didn't invoke the copy constructor for us). Instead,
PLDHashTable explicitly copied around the bits that the copy constructor
would have.

The current setup has two problems:

1) Derived classes should be using move construction, not copy
   construction, since anything that's shuffling hash table keys/entries
   around will be using move construction.

2) Derived classes should take responsibility for transferring bits of
   superclass state around, and not rely on something else to handle that.

The second point is not a huge problem for PLDHashTable (PLDHashTable
only has to copy PLDHashEntryHdr's bits in a single place), but future
hash table implementations that might move entries around more
aggressively would have to insert compensation code all over the
place. Additionally, if moving entries is implemented via memcpy (which
is quite common), PLDHashTable copying around bits *again* is
inefficient.

Let's fix all these problems in one go, by:

1) Explicitly declaring the set of constructors that PLDHashEntryHdr
   implements (and does not implement). In particular, the copy
   constructor is deleted, so any derived classes that attempt to make
   themselves copyable will be detected at compile time: the compiler
   will complain that the superclass type is not copyable.

This change on its own will result in many compiler errors, so...

2) Change any derived classes to implement move constructors instead of
   copy constructors. Note that some of these move constructors are,
   strictly speaking, unnecessary, since the relevant classes are moved
   via memcpy in nsTHashtable and its derivatives.
2018-09-20 11:20:36 -04:00

372 lines
10 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"
using namespace mozilla;
/******************************************************************************
* static hash table callback functions
*
*****************************************************************************/
struct HashTableEntry : PLDHashEntryHdr {
nsDiskCacheBinding * mBinding;
};
static PLDHashNumber
HashKey(const void *key)
{
return (PLDHashNumber) NS_PTR_TO_INT32(key);
}
static bool
MatchEntry(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)
{
new (KnownNotNull, dst) HashTableEntry(std::move(*(HashTableEntry*)src));
// No need to delete `src`.
}
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;
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) const
{
NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
// find hash entry for key
auto hashEntry = static_cast<HashTableEntry*>
(table.Search((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
auto hashEntry = static_cast<HashTableEntry*>
(table.Add((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 (true) {
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;
void* key = (void *)(uintptr_t)binding->mRecord.HashNumber();
auto hashEntry =
static_cast<HashTableEntry*>(table.Search((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
table.Remove((void*)(uintptr_t) binding->mRecord.HashNumber());
return;
}
// 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;
}