blob: 022bc9e90f207397f619dc90b8a78ec107f873d6 [file] [edit]
// Copyright 2026 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 "quiche/common/abiding_object_pool.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "quiche/common/platform/api/quiche_test.h"
namespace quiche {
namespace test {
namespace {
struct DestructionTracker {
int value;
int* destruction_count;
DestructionTracker(int val, int* d_count)
: value(val), destruction_count(d_count) {}
~DestructionTracker() {
if (destruction_count != nullptr) {
++(*destruction_count);
}
}
};
class Base {
public:
virtual ~Base() = default;
virtual std::string Name() const = 0;
protected:
const char* name_ = nullptr;
int* destruction_count_ = nullptr;
};
class Derived : public Base {
public:
explicit Derived(const char* name, int* destruction_count = nullptr) {
name_ = name;
destruction_count_ = destruction_count;
}
~Derived() override {
if (destruction_count_ != nullptr) {
++(*destruction_count_);
}
}
std::string Name() const override { return name_ != nullptr ? name_ : ""; }
};
struct alignas(64) OveralignedStruct {
uint64_t a;
uint64_t b;
};
TEST(AbidingObjectPoolTest, BasicAllocationAndDestruction) {
AbidingObjectPool<DestructionTracker, 1024> pool;
EXPECT_TRUE(pool.empty());
EXPECT_EQ(pool.allocated_slabs(), 0u);
EXPECT_EQ(pool.total_capacity(), 0u);
int destructions = 0;
{
auto ptr = pool.Create(42, &destructions);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(ptr->value, 42);
EXPECT_FALSE(pool.empty());
EXPECT_EQ(pool.allocated_slabs(), 1u);
EXPECT_GT(pool.total_capacity(), 0u);
EXPECT_EQ(destructions, 0);
}
EXPECT_EQ(destructions, 1);
EXPECT_TRUE(pool.empty());
// 1 spare empty slab is retained in reserve.
EXPECT_EQ(pool.allocated_slabs(), 1u);
EXPECT_EQ(pool.num_free_slots(), pool.total_capacity());
}
TEST(AbidingObjectPoolTest, FreelistReuse) {
AbidingObjectPool<int64_t, 256> pool;
void* first_address = nullptr;
{
auto ptr = pool.Create(100);
first_address = static_cast<void*>(ptr.get());
EXPECT_EQ(*ptr, 100);
}
// The next allocation should reuse the most recently deallocated slot (LIFO).
{
auto ptr = pool.Create(200);
EXPECT_EQ(static_cast<void*>(ptr.get()), first_address);
EXPECT_EQ(*ptr, 200);
}
}
TEST(AbidingObjectPoolTest, PolymorphicDerivedCreation) {
AbidingObjectPool<Base, 1024> pool;
int destructions = 0;
{
AbidingObjectPool<Base, 1024>::Ptr<Derived> derived_ptr =
pool.Create<Derived>("quiche_test", &destructions);
ASSERT_NE(derived_ptr, nullptr);
EXPECT_EQ(derived_ptr->Name(), "quiche_test");
EXPECT_EQ(destructions, 0);
}
EXPECT_EQ(destructions, 1);
}
TEST(AbidingObjectPoolTest, AutomaticSlabDrainingAndCompaction) {
constexpr size_t kSlabSize = 256;
AbidingObjectPool<int64_t, kSlabSize> pool;
std::vector<AbidingObjectPool<int64_t, kSlabSize>::Ptr<int64_t>> objects;
// Allocate enough objects to span multiple slabs (5 slabs at ~27 slots/slab).
for (int i = 0; i < 120; ++i) {
objects.push_back(pool.Create(i));
}
size_t peak_slabs = pool.allocated_slabs();
EXPECT_GE(peak_slabs, 4u);
// Free most objects, leaving 20 active (fits within 1 slab).
objects.resize(20);
// Excess drained slabs are destroyed, leaving 1 active slab + 1 spare slab.
EXPECT_LT(pool.allocated_slabs(), peak_slabs);
EXPECT_EQ(pool.allocated_slabs(), 2u);
// Free all remaining objects.
objects.clear();
// Exactly 1 spare empty slab is retained in cache.
EXPECT_EQ(pool.allocated_slabs(), 1u);
EXPECT_TRUE(pool.empty());
EXPECT_EQ(pool.num_free_slots(), pool.total_capacity());
}
TEST(AbidingObjectPoolTest, Preallocation) {
constexpr size_t kInitialSlabs = 3;
constexpr size_t kSlabSize = 512;
AbidingObjectPool<int64_t, kSlabSize> pool(kInitialSlabs);
EXPECT_EQ(pool.allocated_slabs(), kInitialSlabs);
EXPECT_GT(pool.total_capacity(), 0u);
EXPECT_EQ(pool.num_free_slots(), pool.total_capacity());
EXPECT_TRUE(pool.empty());
}
TEST(AbidingObjectPoolTest, MovePtrMaintainsDeleter) {
AbidingObjectPool<int64_t, 256> pool;
auto ptr1 = pool.Create(12345);
ASSERT_NE(ptr1, nullptr);
auto ptr2 = std::move(ptr1);
EXPECT_EQ(ptr1, nullptr);
ASSERT_NE(ptr2, nullptr);
EXPECT_EQ(*ptr2, 12345);
EXPECT_FALSE(pool.empty());
ptr2.reset();
EXPECT_TRUE(pool.empty());
EXPECT_EQ(pool.allocated_slabs(), 1u);
}
TEST(AbidingObjectPoolTest, OveralignedObject) {
AbidingObjectPool<OveralignedStruct, 1024> pool;
auto ptr = pool.Create();
ASSERT_NE(ptr, nullptr);
auto address = reinterpret_cast<uintptr_t>(ptr.get());
EXPECT_EQ(address % alignof(OveralignedStruct), 0u);
}
constinit thread_local AbidingObjectPool<int64_t, 256> kConstinitPool;
TEST(AbidingObjectPoolTest, ConstinitThreadLocal) {
auto ptr = kConstinitPool.Create(42);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(*ptr, 42);
}
} // namespace
} // namespace test
} // namespace quiche