Fix data race in freeing control block

This commit is contained in:
2025-09-11 11:32:59 -04:00
parent 0f179eed88
commit 994e31032f
2 changed files with 73 additions and 12 deletions

View File

@@ -1,3 +1,4 @@
#include <barrier>
#include <doctest/doctest.h>
#include <latch>
#include <thread>
@@ -156,6 +157,63 @@ TEST_CASE("Ref thread safety") {
}
}
TEST_CASE("Control block cleanup race condition test") {
// This test specifically targets the race condition where both
// the last strong reference and last weak reference are destroyed
// simultaneously, potentially causing double-free of control block
const int test_iterations = 10000;
// Shared state for passing references between threads
Ref<TestObject> ptr1;
WeakRef<TestObject> ptr2;
auto setup = [&]() {
ptr1 = make_ref<TestObject>(0);
ptr2 = ptr1;
};
// Barrier for synchronization - 2 participants (main thread + worker thread)
std::barrier sync_barrier{2};
std::thread worker_thread([&]() {
for (int iter = 0; iter < test_iterations; ++iter) {
// Wait for main thread to create the references
sync_barrier.arrive_and_wait();
// Worker thread destroys the weak reference simultaneously with main
// thread
ptr2.reset();
// Wait for next iteration
sync_barrier.arrive_and_wait();
}
});
for (int iter = 0; iter < test_iterations; ++iter) {
// Create references
setup();
// Both threads are ready - synchronize for simultaneous destruction
sync_barrier.arrive_and_wait();
// Main thread destroys the strong reference at the same time
// as worker thread destroys the weak reference
ptr1.reset();
// Wait for both destructions to complete
sync_barrier.arrive_and_wait();
// Clean up for next iteration
ptr1.reset();
ptr2.reset();
}
worker_thread.join();
// If we reach here without segfault/double-free, the test passes
// The bug would manifest as a crash or memory corruption
}
TEST_CASE("WeakRef prevents circular references") {
SUBCASE("simple weak reference lifecycle") {
WeakRef<TestObject> weak_ref;