+// Repro for T#5841499. Race between erase() and find() on the same key.
+TEST(Ahm, erase_find_race) {
+ const uint64_t limit = 10000;
+ AtomicHashMap<uint64_t, uint64_t> map(limit + 10);
+ std::atomic<uint64_t> key {1};
+
+ // Invariant: all values are equal to their keys.
+ // At any moment there is one or two consecutive keys in the map.
+
+ std::thread write_thread([&]() {
+ while (true) {
+ uint64_t k = ++key;
+ if (k > limit) {
+ break;
+ }
+ map.insert(k + 1, k + 1);
+ map.erase(k);
+ }
+ });
+
+ std::thread read_thread([&]() {
+ while (true) {
+ uint64_t k = key.load();
+ if (k > limit) {
+ break;
+ }
+
+ auto it = map.find(k);
+ if (it != map.end()) {
+ ASSERT_EQ(k, it->second);
+ }
+ }
+ });
+
+ read_thread.join();
+ write_thread.join();
+}
+
+// Erase right after insert race bug repro (t9130653)
+TEST(Ahm, erase_after_insert_race) {
+ const uint64_t limit = 10000;
+ const size_t num_threads = 100;
+ const size_t num_iters = 500;
+ AtomicHashMap<uint64_t, uint64_t> map(limit + 10);
+
+ std::atomic<bool> go{false};
+ std::vector<std::thread> ts;
+ for (size_t i = 0; i < num_threads; ++i) {
+ ts.emplace_back([&]() {
+ while (!go) {
+ continue;
+ }
+ for (size_t n = 0; n < num_iters; ++n) {
+ map.erase(1);
+ map.insert(1, 1);
+ }
+ });
+ }
+
+ go = true;
+
+ for (auto& t : ts) {
+ t.join();
+ }
+}
+
+// Repro for a bug when iterator didn't skip empty submaps.
+TEST(Ahm, iterator_skips_empty_submaps) {
+ AtomicHashMap<uint64_t, uint64_t>::Config config;
+ config.growthFactor = 1;
+
+ AtomicHashMap<uint64_t, uint64_t> map(1, config);
+
+ map.insert(1, 1);
+ map.insert(2, 2);
+ map.insert(3, 3);
+
+ map.erase(2);
+
+ auto it = map.find(1);
+
+ ASSERT_NE(map.end(), it);
+ ASSERT_EQ(1, it->first);
+ ASSERT_EQ(1, it->second);
+
+ ++it;
+
+ ASSERT_NE(map.end(), it);
+ ASSERT_EQ(3, it->first);
+ ASSERT_EQ(3, it->second);
+
+ ++it;
+ ASSERT_EQ(map.end(), it);
+}
+