Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1352644
implement new way to use task allocator
LLirikkkk Oct 1, 2026
14fef6e
move runtime to new task allocator api
LLirikkkk Oct 1, 2026
c4f2f4b
change codegen for new task allocator api
LLirikkkk Oct 1, 2026
f4230cd
add new api for kphp::forks::id_managed
LLirikkkk Oct 2, 2026
ce8c4ed
add allocator switching
LLirikkkk Oct 2, 2026
ac39a52
imlement detached task
LLirikkkk Oct 5, 2026
d3a18c9
add constraints for task type
LLirikkkk Oct 5, 2026
e91508b
move code gen and runtime to new api
LLirikkkk Oct 5, 2026
e9f1ce4
fix bug with dangling references in id_managed
LLirikkkk Oct 6, 2026
62ce082
remove redundant branches
LLirikkkk Oct 7, 2026
97fb058
add stack cache
LLirikkkk Oct 8, 2026
e41a89c
refactor control functions
LLirikkkk Oct 9, 2026
d36afa4
refactor control functions
LLirikkkk Oct 9, 2026
96ef18e
refactor object pool
LLirikkkk Oct 9, 2026
cd71f99
reduce number of allocator switches
LLirikkkk Oct 12, 2026
c043364
reduce number of allocator switches
LLirikkkk Oct 12, 2026
7a0c734
reduce number of allocator switches
LLirikkkk Oct 12, 2026
0dd2bce
reduce number of allocator switches
LLirikkkk Oct 12, 2026
5b0cbb3
fix bug with allocator switching in detached-task destructor
LLirikkkk Oct 12, 2026
c3057b1
add stack resource
LLirikkkk Oct 16, 2026
6f43cc2
rewrite task allocator to use stack resource
LLirikkkk Oct 20, 2026
1bf2984
format
LLirikkkk Nov 2, 2026
f8195cc
format
LLirikkkk Nov 2, 2026
6c40fa5
format
LLirikkkk Nov 2, 2026
53da8d1
format
LLirikkkk Nov 2, 2026
9e67bc4
add zero sized chunk handling in object pool
LLirikkkk Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion common/common-tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ prepend(COMMON_TESTS_SOURCES ${COMMON_DIR}/
wrappers/span-test.cpp
wrappers/string_view-test.cpp
ucontext/ucontext-portable-test.cpp
containers/intrusive-list-test.cpp)
containers/intrusive-list-test.cpp
containers/object-pool-test.cpp)

allow_deprecated_declarations(${COMMON_TESTS_SOURCES}/algorithms/projections-test.cpp)

Expand Down
332 changes: 332 additions & 0 deletions common/containers/object-pool-test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2026 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#include <cstddef>
#include <memory>
#include <set>
#include <vector>

#include <gtest/gtest.h>

#include "common/containers/object-pool.h"

namespace {

template<typename T>
struct counting_allocator : std::allocator<T> {
using std::allocator<T>::allocator;

template<typename U>
struct rebind {
using other = counting_allocator<U>;
};

static inline size_t allocate_calls{0};
static inline size_t deallocate_calls{0};

auto allocate(std::size_t n) -> T* {
++allocate_calls;
return std::allocator<T>::allocate(n);
}

auto deallocate(T* p, std::size_t n) -> void {
++deallocate_calls;
std::allocator<T>::deallocate(p, n);
}
};

template<typename T>
struct tagged_allocator : std::allocator<T> {
using std::allocator<T>::allocator;

template<typename U>
struct rebind {
using other = tagged_allocator<U>;
};

static inline std::vector<int> allocation_tags;

int m_tag{0};

tagged_allocator() = default;
explicit tagged_allocator(int tag) noexcept
: m_tag{tag} {}

template<typename U>
tagged_allocator(const tagged_allocator<U>& other) noexcept
: m_tag{other.m_tag} {}

auto allocate(std::size_t n) -> T* {
allocation_tags.push_back(m_tag);
return std::allocator<T>::allocate(n);
}
};

struct point {
int x{};
int y{};

point() = default;
point(int x_val, int y_val) noexcept
: x{x_val},
y{y_val} {}
};

struct dtor_tracker {
bool* m_destroyed{nullptr};

explicit dtor_tracker(bool* destroyed) noexcept
: m_destroyed{destroyed} {}

~dtor_tracker() {
*m_destroyed = true;
}
};

} // namespace

TEST(object_pool_test, acquire_forward_no_constructor_arguments) {
vk::object_pool<int> pool{4};

int* x{pool.acquire()};

ASSERT_EQ(*x, 0);

pool.release(x);
}

TEST(object_pool_test, acquire_forwards_single_constructor_argument) {
vk::object_pool<int> pool{4};

int* x{pool.acquire(42)};

ASSERT_EQ(*x, 42);

pool.release(x);
}

TEST(object_pool_test, acquire_forwards_multiple_constructor_arguments) {
vk::object_pool<point> pool{4};

point* p{pool.acquire(1, 2)};

ASSERT_EQ(p->x, 1);
ASSERT_EQ(p->y, 2);

pool.release(p);
}

TEST(object_pool_test, acquire_forwards_move_only_arguments) {
vk::object_pool<std::unique_ptr<int>> pool{4};

std::unique_ptr<int>* obj{pool.acquire(std::make_unique<int>(5))};

ASSERT_NE(obj, nullptr);
ASSERT_EQ(**obj, 5);

pool.release(obj);
}

TEST(object_pool_test, release_invokes_destructor) {
vk::object_pool<dtor_tracker> pool{4};
bool destroyed{false};

dtor_tracker* obj{pool.acquire(&destroyed)};

ASSERT_FALSE(destroyed);

pool.release(obj);

ASSERT_TRUE(destroyed);
}

TEST(object_pool_test, acquire_after_release_reuses_last_freed_slot) {
vk::object_pool<int> pool{4};

int* a{pool.acquire(1)};
int* addr_a{a};
pool.release(a);

int* b{pool.acquire(2)};

ASSERT_EQ(b, addr_a);

pool.release(b);
}

TEST(object_pool_test, constructor_allocates_exactly_one_chunk) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

vk::object_pool<int, counting_allocator<int>> pool{3};

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 1);

int* x{pool.acquire(7)};
pool.release(x);
}

TEST(object_pool_test, constructor_with_allocator_allocates_exactly_one_chunk) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

vk::object_pool<int, counting_allocator<int>> pool{3, counting_allocator<int>{}};

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 1);

int* x{pool.acquire(7)};
pool.release(x);
}

TEST(object_pool_test, constructor_with_allocator_stores_the_passed_allocator_instance) {
tagged_allocator<std::byte>::allocation_tags.clear();

vk::object_pool<int, tagged_allocator<int>> pool{2, tagged_allocator<int>{42}};

std::vector<int*> ptrs;
for (int i = 0; i < 5; ++i) {
ptrs.push_back(pool.acquire(i));
}

ASSERT_FALSE(tagged_allocator<std::byte>::allocation_tags.empty());
for (int tag : tagged_allocator<std::byte>::allocation_tags) {
ASSERT_EQ(tag, 42);
}

for (auto* p : ptrs) {
pool.release(p);
}
}

TEST(object_pool_test, acquiring_exactly_chunk_size_objects_does_not_grow) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

vk::object_pool<int, counting_allocator<int>> pool{4};
std::vector<int*> ptrs;
for (int i = 0; i < 4; ++i) {
ptrs.push_back(pool.acquire(i));
}

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 1);

for (auto* p : ptrs) {
pool.release(p);
}
}

TEST(object_pool_test, release_then_acquire_does_not_allocate_a_new_chunk) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

vk::object_pool<int, counting_allocator<int>> pool{2};
int* a{pool.acquire(1)};
int* b{pool.acquire(2)};

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 1);

pool.release(a);
int* c{pool.acquire(3)};

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 1);

pool.release(b);
pool.release(c);
}

TEST(object_pool_test, allocates_chunks_when_exchausted) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

vk::object_pool<int, counting_allocator<int>> pool{2};
std::vector<int*> ptrs;
for (int i = 0; i < 5; ++i) {
ptrs.push_back(pool.acquire(i));
}

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 3);

for (auto* p : ptrs) {
pool.release(p);
}
}

TEST(object_pool_test, destructor_deallocates_every_allocated_chunk) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

{
vk::object_pool<int, counting_allocator<int>> pool{2};
std::vector<int*> ptrs;
for (int i = 0; i < 5; ++i) {
ptrs.push_back(pool.acquire(i));
}
for (auto* p : ptrs) {
pool.release(p);
}
}

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, counting_allocator<std::byte>::deallocate_calls);
ASSERT_GE(counting_allocator<std::byte>::allocate_calls, 3);
}

TEST(object_pool_test, acquires_returns_distinct_objects) {
constexpr int total{10};
vk::object_pool<int> pool{2};
std::vector<int*> ptrs;

for (int i = 0; i < total; ++i) {
ptrs.push_back(pool.acquire(i));
}

std::set<int*> unique_ptrs(ptrs.begin(), ptrs.end());
ASSERT_EQ(unique_ptrs.size(), total);

for (int i = 0; i < total; ++i) {
ASSERT_EQ(*ptrs[i], i);
}

for (auto* p : ptrs) {
pool.release(p);
}
}

TEST(object_pool_test, zero_chunk_size_does_not_allocate) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

{
vk::object_pool<int, counting_allocator<int>> pool{0};

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 0);
}

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 0);
ASSERT_EQ(counting_allocator<std::byte>::deallocate_calls, 0);
}

TEST(object_pool_test, zero_chunk_size_with_allocator_does_not_allocate) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

{
vk::object_pool<int, counting_allocator<int>> pool{0, counting_allocator<int>{}};

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 0);
}

ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 0);
ASSERT_EQ(counting_allocator<std::byte>::deallocate_calls, 0);
}

TEST(object_pool_test, zero_chunk_size_acquire_returns_nullptr) {
counting_allocator<std::byte>::allocate_calls = 0;
counting_allocator<std::byte>::deallocate_calls = 0;

vk::object_pool<int, counting_allocator<int>> pool{0};

ASSERT_EQ(pool.acquire(), nullptr);
ASSERT_EQ(pool.acquire(42), nullptr);
ASSERT_EQ(counting_allocator<std::byte>::allocate_calls, 0);
}
Loading
Loading