Skip to content

Commit b2fcffb

Browse files
committed
add emplace() test when tehre is no free slots (tombstones only)
1 parent b56116c commit b2fcffb

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(TEST_SOURCES
2020
ExcaliburHashTest04.cpp
2121
ExcaliburHashTest05.cpp
2222
ExcaliburHashTest06.cpp
23+
ExcaliburHashTest07.cpp
2324
)
2425

2526
set (TEST_EXE_NAME ${PROJ_NAME})

ExcaliburHashTest07.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include "ExcaliburHash.h"
2+
#include <chrono>
3+
#include <gtest/gtest.h>
4+
#include <thread>
5+
6+
using namespace Excalibur;
7+
8+
// Test case for the infinite loop bug when hash table contains only tombstones
9+
TEST(ExcaliburHashInfiniteLoopTest, EmplaceIntoTableWithOnlyTombstones)
10+
{
11+
HashMap<int32_t, int32_t, 1> hashMap;
12+
hashMap.reserve(16);
13+
14+
// Let's first understand the exact growth behavior by testing incrementally
15+
EXPECT_EQ(hashMap.capacity(), 16); // Should start with minimum capacity
16+
17+
// "poison" hash_map - filling it up with tombstones, but always keep one element alive to prevent internal optimizations
18+
hashMap.emplace(-1, -100);
19+
for (int32_t i = 0; i < 256; ++i)
20+
{
21+
if (hashMap.getNumTombstones() == hashMap.capacity() - 1)
22+
{
23+
break;
24+
}
25+
26+
hashMap.emplace(i, i * 10);
27+
hashMap.erase(i);
28+
}
29+
30+
// the hash map is now poisoned... no more free slots left
31+
if (hashMap.getNumTombstones() == hashMap.capacity() - 1)
32+
{
33+
34+
EXPECT_EQ(hashMap.size(), 1);
35+
EXPECT_EQ(hashMap.capacity(), hashMap.getNumTombstones() + 1);
36+
37+
// Set up a timeout to catch infinite loops
38+
std::atomic<bool> completed{false};
39+
std::atomic<bool> timed_out{false};
40+
41+
// Run the potentially infinite operation in a separate thread
42+
std::thread test_thread(
43+
[&]()
44+
{
45+
try
46+
{
47+
// This should trigger the infinite loop bug!
48+
auto result = hashMap.emplace(999, 9999);
49+
EXPECT_TRUE(result.second); // Should be inserted
50+
EXPECT_EQ(result.first.value(), 9999);
51+
completed = true;
52+
}
53+
catch (...)
54+
{
55+
completed = true;
56+
}
57+
});
58+
59+
// Wait for a reasonable time - if it takes longer than 2 seconds,
60+
// we likely have an infinite loop
61+
auto start_time = std::chrono::steady_clock::now();
62+
while (!completed && std::chrono::steady_clock::now() - start_time < std::chrono::seconds(2))
63+
{
64+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
65+
}
66+
67+
if (!completed)
68+
{
69+
timed_out = true;
70+
test_thread.detach();
71+
FAIL() << "Infinite loop detected! emplace() did not complete within 2 seconds. ";
72+
}
73+
else
74+
{
75+
test_thread.join();
76+
if (!timed_out)
77+
{
78+
EXPECT_EQ(hashMap.size(), 1);
79+
EXPECT_TRUE(hashMap.has(999));
80+
}
81+
}
82+
}
83+
else
84+
{
85+
SUCCEED() << "Can't 'poision' hash map";
86+
}
87+
}

0 commit comments

Comments
 (0)