blob: 8e938994fec968636e0ef72227319dd9d0f7248f [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
#include <stdlib.h>
#include <cinttypes>
#include <memory>
#include "base/allocator/allocator_shim.h"
#include "base/debug/alias.h"
#include "base/threading/simple_thread.h"
#include "build/build_config.h"
#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
class LockFreeAddressHashSetTest : public ::testing::Test {
public:
static bool Subset(const LockFreeAddressHashSet& superset,
const LockFreeAddressHashSet& subset) {
for (subtle::AtomicWord bucket : subset.buckets_) {
for (LockFreeAddressHashSet::Node* node =
reinterpret_cast<LockFreeAddressHashSet::Node*>(bucket);
node; node = LockFreeAddressHashSet::next_node(node)) {
void* key = reinterpret_cast<void*>(node->key);
if (key && !superset.Contains(key))
return false;
}
}
return true;
}
static bool Equals(const LockFreeAddressHashSet& set1,
const LockFreeAddressHashSet& set2) {
return Subset(set1, set2) && Subset(set2, set1);
}
static size_t BucketSize(const LockFreeAddressHashSet& set, size_t bucket) {
size_t count = 0;
LockFreeAddressHashSet::Node* node =
reinterpret_cast<LockFreeAddressHashSet::Node*>(set.buckets_[bucket]);
for (; node; node = set.next_node(node))
++count;
return count;
}
};
namespace {
TEST_F(LockFreeAddressHashSetTest, EmptySet) {
LockFreeAddressHashSet set(8);
EXPECT_EQ(size_t(0), set.size());
EXPECT_EQ(size_t(8), set.buckets_count());
EXPECT_EQ(0., set.load_factor());
EXPECT_FALSE(set.Contains(&set));
}
TEST_F(LockFreeAddressHashSetTest, BasicOperations) {
LockFreeAddressHashSet set(8);
for (size_t i = 1; i <= 100; ++i) {
void* ptr = reinterpret_cast<void*>(i);
set.Insert(ptr);
EXPECT_EQ(i, set.size());
EXPECT_TRUE(set.Contains(ptr));
}
size_t size = 100;
EXPECT_EQ(size, set.size());
EXPECT_EQ(size_t(8), set.buckets_count());
EXPECT_EQ(size / 8., set.load_factor());
for (size_t i = 99; i >= 3; i -= 3) {
void* ptr = reinterpret_cast<void*>(i);
set.Remove(ptr);
EXPECT_EQ(--size, set.size());
EXPECT_FALSE(set.Contains(ptr));
}
// Removed every 3rd value (33 total) from the set, 67 have left.
EXPECT_EQ(size_t(67), set.size());
for (size_t i = 1; i <= 100; ++i) {
void* ptr = reinterpret_cast<void*>(i);
EXPECT_EQ(i % 3 != 0, set.Contains(ptr));
}
}
TEST_F(LockFreeAddressHashSetTest, Copy) {
LockFreeAddressHashSet set(16);
for (size_t i = 1000; i <= 16000; i += 1000) {
void* ptr = reinterpret_cast<void*>(i);
set.Insert(ptr);
}
LockFreeAddressHashSet set2(4);
LockFreeAddressHashSet set3(64);
set2.Copy(set);
set3.Copy(set);
EXPECT_TRUE(Equals(set, set2));
EXPECT_TRUE(Equals(set, set3));
EXPECT_TRUE(Equals(set2, set3));
set.Insert(reinterpret_cast<void*>(42));
EXPECT_FALSE(Equals(set, set2));
EXPECT_FALSE(Equals(set, set3));
EXPECT_TRUE(Equals(set2, set3));
EXPECT_TRUE(Subset(set, set2));
EXPECT_FALSE(Subset(set2, set));
}
class WriterThread : public SimpleThread {
public:
WriterThread(LockFreeAddressHashSet* set, subtle::Atomic32* cancel)
: SimpleThread("ReaderThread"), set_(set), cancel_(cancel) {}
void Run() override {
for (size_t value = 42; !subtle::Acquire_Load(cancel_); ++value) {
void* ptr = reinterpret_cast<void*>(value);
set_->Insert(ptr);
EXPECT_TRUE(set_->Contains(ptr));
set_->Remove(ptr);
EXPECT_FALSE(set_->Contains(ptr));
}
// Leave a key for reader to test.
set_->Insert(reinterpret_cast<void*>(0x1337));
}
private:
LockFreeAddressHashSet* set_;
subtle::Atomic32* cancel_;
};
#if defined(THREAD_SANITIZER)
#define DISABLE_ON_TSAN(test_name) DISABLED_##test_name
#else
#define DISABLE_ON_TSAN(test_name) test_name
#endif // defined(THREAD_SANITIZER)
TEST_F(LockFreeAddressHashSetTest, DISABLE_ON_TSAN(ConcurrentAccess)) {
// The purpose of this test is to make sure adding/removing keys concurrently
// does not disrupt the state of other keys.
LockFreeAddressHashSet set(16);
subtle::Atomic32 cancel = 0;
auto thread = std::make_unique<WriterThread>(&set, &cancel);
thread->Start();
for (size_t i = 1; i <= 20; ++i)
set.Insert(reinterpret_cast<void*>(i));
// Remove some items to test empty nodes.
for (size_t i = 16; i <= 20; ++i)
set.Remove(reinterpret_cast<void*>(i));
for (size_t k = 0; k < 100000; ++k) {
for (size_t i = 1; i <= 30; ++i) {
EXPECT_EQ(i < 16, set.Contains(reinterpret_cast<void*>(i)));
}
}
subtle::Release_Store(&cancel, 1);
thread->Join();
EXPECT_TRUE(set.Contains(reinterpret_cast<void*>(0x1337)));
EXPECT_FALSE(set.Contains(reinterpret_cast<void*>(0xbadf00d)));
}
TEST_F(LockFreeAddressHashSetTest, BucketsUsage) {
// Test the uniformity of buckets usage.
size_t count = 10000;
LockFreeAddressHashSet set(16);
for (size_t i = 0; i < count; ++i)
set.Insert(reinterpret_cast<void*>(0x10000 + 0x10 * i));
size_t average_per_bucket = count / set.buckets_count();
for (size_t i = 0; i < set.buckets_count(); ++i) {
size_t usage = BucketSize(set, i);
EXPECT_LT(average_per_bucket * 95 / 100, usage);
EXPECT_GT(average_per_bucket * 105 / 100, usage);
}
}
} // namespace
} // namespace base