Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
concurrent_hash_map_test.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
36#include <gtest/gtest.h>
37
39
41
42#include <atomic>
43#include <map>
44#include <memory>
45#include <stdexcept>
46#include <string>
47#include <thread>
48#include <vector>
49
50using namespace Aleph;
51using namespace Aleph::Testing;
52
53namespace
54{
55 struct Throwing_Copy
56 {
57 static bool should_throw;
58 int value;
59
60 explicit Throwing_Copy(int v) : value(v) {}
61 Throwing_Copy(const Throwing_Copy &other) : value(other.value)
62 {
63 if (should_throw)
64 throw std::runtime_error("Throwing_Copy: copy failed");
65 }
66 Throwing_Copy(Throwing_Copy &&) = default;
67 Throwing_Copy &operator=(const Throwing_Copy &) = default;
68 Throwing_Copy &operator=(Throwing_Copy &&) = default;
69 };
70
71 bool Throwing_Copy::should_throw = false;
72} // namespace
73
74// `run_workers(thread_count, worker)` (launch N independently-indexed
75// threads with a deterministic simultaneous start, exception-safe join,
76// and first-exception rethrow) is provided by concurrency_test_utils.H
77// itself -- Aleph::Testing::run_workers -- and used unqualified here via
78// the `using namespace Aleph::Testing;` above.
79
86
88{
90 EXPECT_TRUE(m.insert("alpha", 1));
91 EXPECT_FALSE(m.insert("alpha", 99));
92 EXPECT_EQ(m.size(), 1u);
93 EXPECT_EQ(*m.find_copy("alpha"), 1); // unchanged by the rejected insert
94}
95
97{
99 EXPECT_TRUE(m.insert("alpha", std::make_unique<int>(10)));
100 bool saw = m.with_value("alpha", [](const std::unique_ptr<int> &p)
101 {
102 ASSERT_NE(p, nullptr);
103 EXPECT_EQ(*p, 10);
104 });
106}
107
109{
111 EXPECT_FALSE(m.contains("alpha"));
112 EXPECT_FALSE(m.find_copy("alpha").has_value());
113
114 m.insert("alpha", 42);
115 EXPECT_TRUE(m.contains("alpha"));
116 auto v = m.find_copy("alpha");
117 ASSERT_TRUE(v.has_value());
118 EXPECT_EQ(*v, 42);
119}
120
122{
124 m.insert_or_assign("alpha", 1);
125 EXPECT_EQ(*m.find_copy("alpha"), 1);
126 m.insert_or_assign("alpha", 2);
127 EXPECT_EQ(*m.find_copy("alpha"), 2);
128 EXPECT_EQ(m.size(), 1u);
129}
130
132{
134 EXPECT_FALSE(m.erase("nope"));
135 m.insert("alpha", 1);
136 EXPECT_TRUE(m.erase("alpha"));
137 EXPECT_FALSE(m.erase("alpha"));
139}
140
142{
144 m.insert("alpha", 7);
145
146 int seen = -1;
147 EXPECT_TRUE(m.with_value("alpha", [&](const int &v) { seen = v; }));
148 EXPECT_EQ(seen, 7);
149
150 bool called = false;
151 EXPECT_FALSE(m.with_value("missing", [&](const int &) { called = true; }));
152 EXPECT_FALSE(called);
153}
154
156{
158 m.insert("alpha", 10);
159 EXPECT_TRUE(m.with_value_mut("alpha", [](int &v) { v += 5; }));
160 EXPECT_EQ(*m.find_copy("alpha"), 15);
161
162 EXPECT_FALSE(m.with_value_mut("missing", [](int &v) { v = 999; }));
163}
164
166{
168 for (int i = 0; i < 100; ++i)
169 m.insert("key" + std::to_string(i), i);
170 EXPECT_EQ(m.size(), 100u);
171
172 m.clear();
174 EXPECT_EQ(m.size(), 0u);
175}
176
178{
180 m.insert("alpha", 1);
181 m.insert("beta", 2);
182
183 auto snap = m.snapshot();
184 ASSERT_EQ(snap.size(), 2u);
185
186 // Mutating the map afterward must not affect the already-taken snapshot.
187 m.insert_or_assign("alpha", 999);
188 m.erase("beta");
189
190 std::map<std::string, int> as_map;
191 for (const auto &entry : snap)
192 as_map.emplace(entry.first, entry.second);
193 ASSERT_EQ(as_map.size(), 2u);
194 EXPECT_EQ(as_map.at("alpha"), 1);
195 EXPECT_EQ(as_map.at("beta"), 2);
196}
197
199{
201 Throwing_Copy value(1);
202 Throwing_Copy::should_throw = false;
204
205 Throwing_Copy::should_throw = true;
206 Throwing_Copy other(2);
207 EXPECT_THROW(m.insert(2, other), std::runtime_error);
208 Throwing_Copy::should_throw = false;
209
210 EXPECT_EQ(m.size(), 1u);
213}
214
216{
217 // Two keys deliberately chosen to land in different shards (with 16
218 // shards and Aleph's default hash, consecutive small integers spread
219 // across shards); the point of this test is exercising real concurrent
220 // writers below under TSan, not the exact shard placement.
221 constexpr size_t shards = 16;
223
224 run_workers(shards, [&](size_t idx)
225 {
226 for (int i = 0; i < 200; ++i)
227 {
228 const int key = static_cast<int>(idx) * 1000 + i;
229 m.insert(key, i);
230 }
231 });
232
233 EXPECT_EQ(m.size(), shards * 200);
234}
235
237{
238 constexpr int thread_count = 8;
239 constexpr int keys_per_thread = 2000;
241
242 run_workers(thread_count, [&](size_t t)
243 {
244 const int base = static_cast<int>(t) * keys_per_thread;
245
246 for (int i = 0; i < keys_per_thread; ++i)
247 {
248 const int key = base + i;
249 EXPECT_TRUE(m.insert(key, i));
250 EXPECT_TRUE(m.contains(key));
251 auto v = m.find_copy(key);
252 ASSERT_TRUE(v.has_value());
253 EXPECT_EQ(*v, i);
254 }
255
256 for (int i = 0; i < keys_per_thread; ++i)
257 {
258 const int key = base + i;
259 EXPECT_TRUE(m.with_value_mut(key, [](int &v) { v *= 2; }));
260 }
261
262 for (int i = 0; i < keys_per_thread; i += 2) // erase every other key
263 EXPECT_TRUE(m.erase(base + i));
264 });
265
266 const size_t expected = static_cast<size_t>(thread_count) * (keys_per_thread / 2);
268
269 for (int t = 0; t < thread_count; ++t)
270 for (int i = 0; i < keys_per_thread; ++i)
271 {
272 const int key = t * keys_per_thread + i;
273 auto v = m.find_copy(key);
274 if (i % 2 == 0)
275 EXPECT_FALSE(v.has_value());
276 else
277 {
278 ASSERT_TRUE(v.has_value());
279 EXPECT_EQ(*v, i * 2);
280 }
281 }
282}
283
285{
287 4000, 0xBADC0FFEu, 200,
288 {
289 Trace_Operation_Kind::insert,
290 Trace_Operation_Kind::erase,
291 Trace_Operation_Kind::contains
292 });
293
294 std::map<size_t, size_t> reference;
296
297 auto apply_reference = [](std::map<size_t, size_t> &ref, const Trace_Operation &op) -> bool
298 {
299 switch (op.kind)
300 {
301 case Trace_Operation_Kind::insert:
302 return ref.emplace(op.key, op.value).second;
303 case Trace_Operation_Kind::erase:
304 return ref.erase(op.key) != 0;
305 case Trace_Operation_Kind::contains:
306 return ref.find(op.key) != ref.end();
307 default:
308 return false;
309 }
310 };
311
313 {
314 switch (op.kind)
315 {
316 case Trace_Operation_Kind::insert:
317 return s.insert(op.key, op.value);
318 case Trace_Operation_Kind::erase:
319 return s.erase(op.key);
320 case Trace_Operation_Kind::contains:
321 return s.contains(op.key);
322 default:
323 return false;
324 }
325 };
326
327 for (size_t i = 0; i < trace.size(); ++i)
328 {
329 const bool ref_result = apply_reference(reference, trace[i]);
330 const bool subj_result = apply_subject(subject, trace[i]);
331 ASSERT_EQ(ref_result, subj_result) << "mismatch at operation " << i;
332 }
333
334 ASSERT_EQ(subject.size(), reference.size());
335 for (const auto &[key, value] : reference)
336 {
337 auto v = subject.find_copy(key);
338 ASSERT_TRUE(v.has_value()) << "missing key " << key;
339 EXPECT_EQ(*v, value);
340 }
341}
size_t size_t int32_t value
Definition ca-c-api.h:116
Sharded concurrent hash map: Shards independently-locked DynMapHashTable partitions,...
bool contains(const Key &key) const
Check whether key is present.
bool insert(const Key &key, const T &value)
Insert key with a copy of value, only if key is absent.
bool erase(const Key &key)
Remove key if present.
constexpr bool contains(const Key &key) const noexcept
Alias for has().
Definition hashDry.H:425
Minimal std::expected-style result type for C++20.
void emplace(Args &&...args)
Appends a new element into the container by constructing it in-place with the given args.
Definition ah-dry.H:694
constexpr size_t size() const noexcept
Returns the number of entries in the table.
Definition hashDry.H:619
void clear()
Empties the container.
Definition hashDry.H:614
constexpr bool is_empty() const noexcept
Checks if the table is empty.
Definition hashDry.H:624
Key * insert(const Key &key)
Inserts a key into the hash table (copy version).
Definition hashDry.H:203
Reusable helpers for concurrent data-structure tests.
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
std::vector< Trace_Operation > make_random_operation_trace(const size_t count, const uint32_t seed, const size_t key_range, const std::initializer_list< Trace_Operation_Kind > kinds={ Trace_Operation_Kind::insert, Trace_Operation_Kind::erase, Trace_Operation_Kind::contains })
Build a deterministic pseudo-random operation trace.
void run_workers(const size_t thread_count, Worker worker)
Launch thread_count workers with a deterministic simultaneous start, join every one of them,...
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
One randomized operation trace entry.
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
Sharded concurrent hash map (Aleph::ConcurrentHashMap).