Bug 1173600 (part 3) - Add PLDHashTable::RemovingIterator. r=froydnj.

This commit is contained in:
Nicholas Nethercote 2015-06-10 17:04:07 -07:00
parent b0a39a3a7c
commit a2d83b8f85
3 changed files with 132 additions and 1 deletions

View File

@ -978,6 +978,39 @@ PLDHashTable::Iterator::Next()
} while (IsOnNonLiveEntry());
}
PLDHashTable::RemovingIterator::RemovingIterator(RemovingIterator&& aOther)
: Iterator(mozilla::Move(aOther.mTable))
, mHaveRemoved(aOther.mHaveRemoved)
{
}
PLDHashTable::RemovingIterator::RemovingIterator(PLDHashTable* aTable)
: Iterator(aTable)
, mHaveRemoved(false)
{
}
PLDHashTable::RemovingIterator::~RemovingIterator()
{
if (mHaveRemoved) {
// Why is this cast needed? In Iterator, |mTable| is const. In
// RemovingIterator it should be non-const, but it inherits from Iterator
// so that's not possible. But it's ok because RemovingIterator's
// constructor takes a pointer to a non-const table in the first place.
const_cast<PLDHashTable*>(mTable)->ShrinkIfAppropriate();
}
}
void
PLDHashTable::RemovingIterator::Remove()
{
METER(mStats.mRemoveEnums++);
// This cast is needed for the same reason as the one in the destructor.
const_cast<PLDHashTable*>(mTable)->RawRemove(Get());
mHaveRemoved = true;
}
#ifdef DEBUG
MOZ_ALWAYS_INLINE void
PLDHashTable::MarkImmutable()

View File

@ -323,8 +323,10 @@ public:
PLDHashEntryHdr* Get() const; // Get the current entry.
void Next(); // Advance to the next entry.
private:
protected:
const PLDHashTable* mTable; // Main table pointer.
private:
char* mCurrent; // Pointer to the current entry.
char* mLimit; // One past the last entry.
@ -338,6 +340,35 @@ public:
Iterator Iter() const { return Iterator(this); }
// This is an iterator that allows elements to be removed during iteration.
// If any elements are removed, the table may be resized once iteration ends.
// Its usage is similar to that of Iterator, with the addition that Remove()
// can be called once per element.
class RemovingIterator : public Iterator
{
public:
explicit RemovingIterator(PLDHashTable* aTable);
RemovingIterator(RemovingIterator&& aOther);
~RemovingIterator();
// Remove the current entry. Must only be called once per entry, and Get()
// must not be called on that entry afterwards.
void Remove();
private:
bool mHaveRemoved; // Have any elements been removed?
RemovingIterator() = delete;
RemovingIterator(const RemovingIterator&) = delete;
RemovingIterator& operator=(const RemovingIterator&) = delete;
RemovingIterator& operator=(const RemovingIterator&&) = delete;
};
RemovingIterator RemovingIter() const
{
return RemovingIterator(const_cast<PLDHashTable*>(this));
}
private:
static bool EntryIsFree(PLDHashEntryHdr* aEntry);

View File

@ -231,6 +231,72 @@ static bool test_pldhash_Iterator()
return true;
}
static bool test_pldhash_RemovingIterator()
{
PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub));
// Explicitly test the move constructor. We do this because, due to copy
// elision, compilers might optimize away move constructor calls for normal
// iterator use.
{
PLDHashTable::Iterator iter1(&t);
PLDHashTable::Iterator iter2(mozilla::Move(iter1));
}
// First, we insert 64 items, which results in a capacity of 128, and a load
// factor of 50%.
for (intptr_t i = 0; i < 64; i++) {
PL_DHashTableAdd(&t, (const void*)i);
}
if (t.EntryCount() != 64 || t.Capacity() != 128) {
return false;
}
// The first removing iterator does no removing; capacity and entry count are
// unchanged.
for (PLDHashTable::RemovingIterator iter(&t); !iter.Done(); iter.Next()) {
(void) iter.Get();
}
if (t.EntryCount() != 64 || t.Capacity() != 128) {
return false;
}
// The second removing iterator removes 16 items. This reduces the load
// factor to 37.5% (48 / 128), which isn't low enough to shrink the table.
for (auto iter = t.RemovingIter(); !iter.Done(); iter.Next()) {
auto entry = static_cast<PLDHashEntryStub*>(iter.Get());
if ((intptr_t)(entry->key) % 4 == 0) {
iter.Remove();
}
}
if (t.EntryCount() != 48 || t.Capacity() != 128) {
return false;
}
// The third removing iterator removes another 16 items. This reduces
// the load factor to 25% (32 / 128), so the table is shrunk.
for (auto iter = t.RemovingIter(); !iter.Done(); iter.Next()) {
auto entry = static_cast<PLDHashEntryStub*>(iter.Get());
if ((intptr_t)(entry->key) % 2 == 0) {
iter.Remove();
}
}
if (t.EntryCount() != 32 || t.Capacity() != 64) {
return false;
}
// The fourth removing iterator removes all remaining items. This reduces
// the capacity to the minimum.
for (auto iter = t.RemovingIter(); !iter.Done(); iter.Next()) {
iter.Remove();
}
if (t.EntryCount() != 0 || t.Capacity() != PL_DHASH_MIN_CAPACITY) {
return false;
}
return true;
}
// See bug 931062, we skip this test on Android due to OOM.
#ifndef MOZ_WIDGET_ANDROID
static bool test_pldhash_grow_to_max_capacity()
@ -274,6 +340,7 @@ static const struct Test {
DECL_TEST(test_pldhash_move_semantics),
DECL_TEST(test_pldhash_Clear),
DECL_TEST(test_pldhash_Iterator),
DECL_TEST(test_pldhash_RemovingIterator),
// See bug 931062, we skip this test on Android due to OOM. Also, it's slow,
// and so should always be last.
#ifndef MOZ_WIDGET_ANDROID