1 /*
2 Copyright (c) 2005-2023 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #if _MSC_VER
18 #if __INTEL_COMPILER
19 #pragma warning(disable : 2586) // decorated name length exceeded, name was truncated
20 #else
21 // Workaround for vs2015 and warning name was longer than the compiler limit (4096).
22 #pragma warning (disable: 4503)
23 #endif
24 #endif
25
26 #define TBB_DEFINE_STD_HASH_SPECIALIZATIONS 1
27 #define TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS 1
28 #include <common/test.h>
29 #include <common/utils.h>
30 #include <common/range_based_for_support.h>
31 #include <common/custom_allocators.h>
32 #include <common/containers_common.h>
33 #include <common/concepts_common.h>
34 #include <tbb/concurrent_hash_map.h>
35 #include <tbb/parallel_for.h>
36 #include <common/concurrent_associative_common.h>
37 #include <vector>
38 #include <list>
39 #include <algorithm>
40 #include <functional>
41 #include <scoped_allocator>
42 #include <mutex>
43
44 //! \file test_concurrent_hash_map.cpp
45 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification
46
TestRangeBasedFor()47 void TestRangeBasedFor(){
48 using namespace range_based_for_support_tests;
49
50 INFO("testing range based for loop compatibility \n");
51 using ch_map = tbb::concurrent_hash_map<int,int>;
52 ch_map a_ch_map;
53
54 const int sequence_length = 100;
55 for (int i = 1; i <= sequence_length; ++i){
56 a_ch_map.insert(ch_map::value_type(i,i));
57 }
58
59 REQUIRE_MESSAGE((range_based_for_accumulate(a_ch_map, pair_second_summer(), 0) == gauss_summ_of_int_sequence(sequence_length)),
60 "incorrect accumulated value generated via range based for ?");
61 }
62
63 // The helper to run a test only when a default construction is present.
64 template <bool default_construction_present> struct do_default_construction_test {
operator ()do_default_construction_test65 template<typename FuncType> void operator() ( FuncType func ) const { func(); }
66 };
67
68 template <> struct do_default_construction_test<false> {
operator ()do_default_construction_test69 template<typename FuncType> void operator()( FuncType ) const {}
70 };
71
72 template <typename Table>
73 class test_insert_by_key {
74 using value_type = typename Table::value_type;
75 Table &my_c;
76 const value_type &my_value;
77 public:
test_insert_by_key(Table & c,const value_type & value)78 test_insert_by_key( Table &c, const value_type &value ) : my_c(c), my_value(value) {}
operator ()() const79 void operator()() const {
80 {
81 typename Table::accessor a;
82 CHECK(my_c.insert( a, my_value.first ));
83 CHECK(utils::IsEqual()(a->first, my_value.first));
84 a->second = my_value.second;
85 }
86 {
87 typename Table::const_accessor ca;
88 CHECK(!my_c.insert( ca, my_value.first ));
89 CHECK(utils::IsEqual()(ca->first, my_value.first));
90 CHECK(utils::IsEqual()(ca->second, my_value.second));
91 }
92 }
93 };
94
95 template <typename Table, typename Iterator, typename Range = typename Table::range_type>
96 class test_range {
97 using value_type = typename Table::value_type;
98 Table &my_c;
99 const std::list<value_type> &my_lst;
100 std::vector<detail::atomic_type<bool>>& my_marks;
101 public:
test_range(Table & c,const std::list<value_type> & lst,std::vector<detail::atomic_type<bool>> & marks)102 test_range( Table &c, const std::list<value_type> &lst, std::vector<detail::atomic_type<bool>> &marks ) : my_c(c), my_lst(lst), my_marks(marks) {
103 for (std::size_t i = 0; i < my_marks.size(); ++i) {
104 my_marks[i].store(false, std::memory_order_relaxed);
105 }
106 }
107
operator ()(const Range & r) const108 void operator()( const Range &r ) const { do_test_range( r.begin(), r.end() ); }
do_test_range(Iterator i,Iterator j) const109 void do_test_range( Iterator i, Iterator j ) const {
110 for ( Iterator it = i; it != j; ) {
111 Iterator it_prev = it++;
112 typename std::list<value_type>::const_iterator it2 = std::search( my_lst.begin(), my_lst.end(), it_prev, it, utils::IsEqual() );
113 CHECK(it2 != my_lst.end());
114 typename std::list<value_type>::difference_type dist = std::distance( my_lst.begin(), it2 );
115 CHECK(!my_marks[dist]);
116 my_marks[dist].store(true);
117 }
118 }
119 };
120
121 template <bool default_construction_present, typename Table>
122 class check_value {
123 using const_iterator = typename Table::const_iterator;
124 using iterator = typename Table::iterator;
125 using size_type = typename Table::size_type;
126 Table &my_c;
127 public:
check_value(Table & c)128 check_value( Table &c ) : my_c(c) {}
operator ()(const typename Table::value_type & value)129 void operator()(const typename Table::value_type &value ) {
130 const Table &const_c = my_c;
131 CHECK(my_c.count( value.first ) == 1);
132 { // tests with a const accessor.
133 typename Table::const_accessor ca;
134 // find
135 CHECK(my_c.find( ca, value.first ));
136 CHECK(!ca.empty() );
137 CHECK(utils::IsEqual()(ca->first, value.first));
138 CHECK(utils::IsEqual()(ca->second, value.second));
139 // erase
140 CHECK(my_c.erase( ca ));
141 CHECK(my_c.count( value.first ) == 0);
142 // insert (pair)
143 CHECK(my_c.insert( ca, value ));
144 CHECK(utils::IsEqual()(ca->first, value.first));
145 CHECK(utils::IsEqual()(ca->second, value.second));
146 } { // tests with a non-const accessor.
147 typename Table::accessor a;
148 // find
149 CHECK(my_c.find( a, value.first ));
150 CHECK(!a.empty() );
151 CHECK(utils::IsEqual()(a->first, value.first));
152 CHECK(utils::IsEqual()(a->second, value.second));
153 // erase
154 CHECK(my_c.erase( a ));
155 CHECK(my_c.count( value.first ) == 0);
156 // insert
157 CHECK(my_c.insert( a, value ));
158 CHECK(utils::IsEqual()(a->first, value.first));
159 CHECK(utils::IsEqual()(a->second, value.second));
160 }
161 // erase by key
162 CHECK(my_c.erase( value.first ));
163 CHECK(my_c.count( value.first ) == 0);
164 do_default_construction_test<default_construction_present>()(test_insert_by_key<Table>( my_c, value ));
165 // insert by value
166 CHECK(my_c.insert( value ) != default_construction_present);
167 // equal_range
168 std::pair<iterator,iterator> r1 = my_c.equal_range( value.first );
169 iterator r1_first_prev = r1.first++;
170 CHECK((utils::IsEqual()( *r1_first_prev, value ) && utils::IsEqual()( r1.first, r1.second )));
171 std::pair<const_iterator,const_iterator> r2 = const_c.equal_range( value.first );
172 const_iterator r2_first_prev = r2.first++;
173 CHECK((utils::IsEqual()( *r2_first_prev, value ) && utils::IsEqual()( r2.first, r2.second )));
174 }
175 };
176
177 template <typename Value, typename U = Value>
178 struct CompareTables {
179 template <typename T>
IsEqualCompareTables180 static bool IsEqual( const T& t1, const T& t2 ) {
181 return (t1 == t2) && !(t1 != t2);
182 }
183 };
184
185 template <typename U>
186 struct CompareTables< std::pair<const std::weak_ptr<U>, std::weak_ptr<U> > > {
187 template <typename T>
IsEqualCompareTables188 static bool IsEqual( const T&, const T& ) {
189 /* do nothing for std::weak_ptr */
190 return true;
191 }
192 };
193
194 template <bool default_construction_present, typename Table>
Examine(Table c,const std::list<typename Table::value_type> & lst)195 void Examine( Table c, const std::list<typename Table::value_type> &lst) {
196 using const_table = const Table;
197 using const_iterator = typename Table::const_iterator;
198 using iterator = typename Table::iterator;
199 using value_type = typename Table::value_type;
200 using size_type = typename Table::size_type;
201
202 CHECK(!c.empty());
203 CHECK(c.size() == lst.size());
204 CHECK(c.max_size() >= c.size());
205
206 const check_value<default_construction_present, Table> cv(c);
207 std::for_each( lst.begin(), lst.end(), cv );
208
209 std::vector<detail::atomic_type<bool>> marks( lst.size() );
210
211 test_range<Table,iterator>( c, lst, marks ).do_test_range( c.begin(), c.end() );
212 CHECK(std::find( marks.begin(), marks.end(), false ) == marks.end());
213
214 test_range<const_table,const_iterator>( c, lst, marks ).do_test_range( c.begin(), c.end() );
215 CHECK(std::find( marks.begin(), marks.end(), false ) == marks.end());
216
217 using range_type = typename Table::range_type;
218 tbb::parallel_for( c.range(), test_range<Table,typename range_type::iterator,range_type>( c, lst, marks ) );
219 CHECK(std::find( marks.begin(), marks.end(), false ) == marks.end());
220
221 const_table const_c = c;
222 CHECK(CompareTables<value_type>::IsEqual( c, const_c ));
223
224 const size_type new_bucket_count = 2*c.bucket_count();
225 c.rehash( new_bucket_count );
226 CHECK(c.bucket_count() >= new_bucket_count);
227
228 Table c2;
229 typename std::list<value_type>::const_iterator begin5 = lst.begin();
230 std::advance( begin5, 5 );
231 c2.insert( lst.begin(), begin5 );
232 std::for_each( lst.begin(), begin5, check_value<default_construction_present, Table>( c2 ) );
233
234 c2.swap( c );
235 CHECK(CompareTables<value_type>::IsEqual( c2, const_c ));
236 CHECK(c.size() == 5);
237 std::for_each( lst.begin(), lst.end(), check_value<default_construction_present,Table>(c2) );
238
239 swap( c, c2 );
240 CHECK(CompareTables<value_type>::IsEqual( c, const_c ));
241 CHECK(c2.size() == 5);
242
243 c2.clear();
244 CHECK(CompareTables<value_type>::IsEqual( c2, Table() ));
245
246 typename Table::allocator_type a = c.get_allocator();
247 value_type *ptr = a.allocate(1);
248 CHECK(ptr);
249 a.deallocate( ptr, 1 );
250 }
251
252 template <typename T>
253 struct debug_hash_compare : public tbb::detail::d1::tbb_hash_compare<T> {};
254
255 template <bool default_construction_present, typename Value>
TypeTester(const std::list<Value> & lst)256 void TypeTester( const std::list<Value> &lst ) {
257 using first_type = typename Value::first_type;
258 using key_type = typename std::remove_const<first_type>::type;
259 using second_type = typename Value::second_type;
260 using ch_map = tbb::concurrent_hash_map<key_type, second_type>;
261 debug_hash_compare<key_type> compare{};
262 // Construct an empty hash map.
263 ch_map c1;
264 c1.insert( lst.begin(), lst.end() );
265 Examine<default_construction_present>( c1, lst );
266
267 // Constructor from initializer_list.
268 typename std::list<Value>::const_iterator it = lst.begin();
269 std::initializer_list<Value> il = { *it++, *it++, *it++ };
270 ch_map c2( il );
271 c2.insert( it, lst.end() );
272 Examine<default_construction_present>( c2, lst );
273
274 // Constructor from initializer_list and compare object
275 ch_map c3( il, compare);
276 c3.insert( it, lst.end() );
277 Examine<default_construction_present>( c3, lst );
278
279 // Constructor from initializer_list, compare object and allocator
280 ch_map c4( il, compare, typename ch_map::allocator_type());
281 c4.insert( it, lst.end());
282 Examine<default_construction_present>( c4, lst );
283
284 // Copying constructor.
285 ch_map c5(c1);
286 Examine<default_construction_present>( c5, lst );
287 // Construct with non-default allocator
288 using ch_map_debug_alloc = tbb::concurrent_hash_map<key_type, second_type,
289 tbb::detail::d1::tbb_hash_compare<key_type>,
290 LocalCountingAllocator<std::allocator<Value>>>;
291 ch_map_debug_alloc c6;
292 c6.insert( lst.begin(), lst.end() );
293 Examine<default_construction_present>( c6, lst );
294 // Copying constructor
295 ch_map_debug_alloc c7(c6);
296 Examine<default_construction_present>( c7, lst );
297 // Construction empty table with n preallocated buckets.
298 ch_map c8( lst.size() );
299 c8.insert( lst.begin(), lst.end() );
300 Examine<default_construction_present>( c8, lst );
301 ch_map_debug_alloc c9( lst.size() );
302 c9.insert( lst.begin(), lst.end() );
303 Examine<default_construction_present>( c9, lst );
304 // Construction with copying iteration range.
305 ch_map c10_1( c1.begin(), c1.end() ), c10_2(c1.cbegin(), c1.cend());
306 Examine<default_construction_present>( c10_1, lst );
307 Examine<default_construction_present>( c10_2, lst );
308 // Construction with copying iteration range and given allocator instance.
309 LocalCountingAllocator<std::allocator<Value>> allocator;
310 ch_map_debug_alloc c11( lst.begin(), lst.end(), allocator );
311 Examine<default_construction_present>( c11, lst );
312
313 using ch_map_debug_hash = tbb::concurrent_hash_map<key_type, second_type,
314 debug_hash_compare<key_type>,
315 typename ch_map::allocator_type>;
316
317 // Constructor with two iterators and hash_compare
318 ch_map_debug_hash c12(c1.begin(), c1.end(), compare);
319 Examine<default_construction_present>( c12, lst );
320
321 ch_map_debug_hash c13(c1.begin(), c1.end(), compare, typename ch_map::allocator_type());
322 Examine<default_construction_present>( c13, lst );
323 }
324
TestSpecificTypes()325 void TestSpecificTypes() {
326 const int NUMBER = 10;
327
328 using int_int_t = std::pair<const int, int>;
329 std::list<int_int_t> arrIntInt;
330 for ( int i=0; i<NUMBER; ++i ) arrIntInt.push_back( int_int_t(i, NUMBER-i) );
331 TypeTester</*default_construction_present = */true>( arrIntInt );
332
333 using ref_int_t = std::pair<const std::reference_wrapper<const int>, int>;
334 std::list<ref_int_t> arrRefInt;
335 for ( std::list<int_int_t>::iterator it = arrIntInt.begin(); it != arrIntInt.end(); ++it )
336 arrRefInt.push_back( ref_int_t( it->first, it->second ) );
337 TypeTester</*default_construction_present = */true>( arrRefInt );
338
339 using int_ref_t = std::pair< const int, std::reference_wrapper<int> >;
340 std::list<int_ref_t> arrIntRef;
341 for ( std::list<int_int_t>::iterator it = arrIntInt.begin(); it != arrIntInt.end(); ++it )
342 arrIntRef.push_back( int_ref_t( it->first, it->second ) );
343 TypeTester</*default_construction_present = */false>( arrIntRef );
344
345 using shr_shr_t = std::pair< const std::shared_ptr<int>, std::shared_ptr<int> >;
346 std::list<shr_shr_t> arrShrShr;
347 for ( int i=0; i<NUMBER; ++i ) {
348 const int NUMBER_minus_i = NUMBER - i;
349 arrShrShr.push_back( shr_shr_t( std::make_shared<int>(i), std::make_shared<int>(NUMBER_minus_i) ) );
350 }
351 TypeTester< /*default_construction_present = */true>( arrShrShr );
352
353 using wk_wk_t = std::pair< const std::weak_ptr<int>, std::weak_ptr<int> >;
354 std::list< wk_wk_t > arrWkWk;
355 std::copy( arrShrShr.begin(), arrShrShr.end(), std::back_inserter(arrWkWk) );
356 TypeTester< /*default_construction_present = */true>( arrWkWk );
357
358 // Check working with deprecated hashers
359 using pair_key_type = std::pair<int, int>;
360 using pair_int_t = std::pair<const pair_key_type, int>;
361 std::list<pair_int_t> arr_pair_int;
362 for (int i = 0; i < NUMBER; ++i) {
363 arr_pair_int.push_back(pair_int_t(pair_key_type{i, i}, i));
364 }
365 TypeTester</*default_construction_present = */true>(arr_pair_int);
366
367 using tbb_string_key_type = std::basic_string<char, std::char_traits<char>, tbb::tbb_allocator<char>>;
368 using pair_tbb_string_int_t = std::pair<const tbb_string_key_type, int>;
369 std::list<pair_tbb_string_int_t> arr_pair_string_int;
370 for (int i = 0; i < NUMBER; ++i) {
371 tbb_string_key_type key(i, char(i));
372 arr_pair_string_int.push_back(pair_tbb_string_int_t(key, i));
373 }
374 TypeTester</*default_construction_present = */true>(arr_pair_string_int);
375 }
376
377 struct custom_hash_compare {
378 template<typename Allocator>
hashcustom_hash_compare379 size_t hash(const AllocatorAwareData<Allocator>& key) const {
380 return my_hash_compare.hash(key.value());
381 }
382
383 template<typename Allocator>
equalcustom_hash_compare384 bool equal(const AllocatorAwareData<Allocator>& key1, const AllocatorAwareData<Allocator>& key2) const {
385 return my_hash_compare.equal(key1.value(), key2.value());
386 }
387
388 private:
389 tbb::tbb_hash_compare<int> my_hash_compare;
390 };
391
TestScopedAllocator()392 void TestScopedAllocator() {
393 using allocator_data_type = AllocatorAwareData<std::scoped_allocator_adaptor<tbb::tbb_allocator<int>>>;
394 using allocator_type = std::scoped_allocator_adaptor<tbb::tbb_allocator<std::pair<const allocator_data_type, allocator_data_type>>>;
395 using hash_map_type = tbb::concurrent_hash_map<allocator_data_type, allocator_data_type,
396 custom_hash_compare, allocator_type>;
397
398 allocator_type allocator;
399 allocator_data_type key1(1, allocator), key2(2, allocator);
400 allocator_data_type data1(1, allocator), data2(data1, allocator);
401 hash_map_type map1(allocator), map2(allocator);
402
403 hash_map_type::value_type v1(key1, data1), v2(key2, data2);
404
405 auto init_list = { v1, v2 };
406
407 allocator_data_type::assert_on_constructions = true;
408 map1.emplace(key1, data1);
409 map2.emplace(key2, std::move(data2));
410
411 map1.clear();
412 map2.clear();
413
414 map1.insert(v1);
415 map2.insert(std::move(v2));
416
417 map1.clear();
418 map2.clear();
419
420 map1.insert(init_list);
421
422 map1.clear();
423 map2.clear();
424
425 hash_map_type::accessor a;
426 map2.insert(a, allocator_data_type(3));
427 a.release();
428
429 map1 = map2;
430 map2 = std::move(map1);
431
432 hash_map_type map3(allocator);
433 map3.rehash(1000);
434 map3 = map2;
435 }
436
437 // A test for undocumented member function internal_fast_find
438 // which is declared protected in concurrent_hash_map for internal TBB use
TestInternalFastFind()439 void TestInternalFastFind() {
440 typedef tbb::concurrent_hash_map<int, int> basic_chmap_type;
441 typedef basic_chmap_type::const_pointer const_pointer;
442
443 class chmap : public basic_chmap_type {
444 public:
445 chmap() : basic_chmap_type() {}
446
447 using basic_chmap_type::internal_fast_find;
448 };
449
450 chmap m;
451 int sz = 100;
452
453 for (int i = 0; i != sz; ++i) {
454 m.insert(std::make_pair(i, i * i));
455 }
456 REQUIRE_MESSAGE(m.size() == 100, "Incorrect concurrent_hash_map size");
457
458 for (int i = 0; i != sz; ++i) {
459 const_pointer res = m.internal_fast_find(i);
460 REQUIRE_MESSAGE(res != nullptr, "Incorrect internal_fast_find return value for existing key");
461 basic_chmap_type::value_type val = *res;
462 REQUIRE_MESSAGE(val.first == i, "Incorrect key in internal_fast_find return value");
463 REQUIRE_MESSAGE(val.second == i * i, "Incorrect mapped in internal_fast_find return value");
464 }
465
466 for (int i = sz; i != 2 * sz; ++i) {
467 const_pointer res = m.internal_fast_find(i);
468 REQUIRE_MESSAGE(res == nullptr, "Incorrect internal_fast_find return value for not existing key");
469 }
470 }
471
472 struct default_container_traits {
473 template <typename container_type, typename iterator_type>
construct_containerdefault_container_traits474 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
475 container_type* ptr = reinterpret_cast<container_type*>(&storage);
476 new (ptr) container_type(begin, end);
477 return *ptr;
478 }
479
480 template <typename container_type, typename iterator_type, typename allocator_type>
construct_containerdefault_container_traits481 static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
482 container_type* ptr = reinterpret_cast<container_type*>(&storage);
483 new (ptr) container_type(begin, end, a);
484 return *ptr;
485 }
486 };
487
488 struct hash_map_traits : default_container_traits {
489 enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
490
491 template<typename T>
492 struct hash_compare {
equalhash_map_traits::hash_compare493 bool equal( const T& lhs, const T& rhs ) const {
494 return lhs==rhs;
495 }
hashhash_map_traits::hash_compare496 size_t hash( const T& k ) const {
497 return my_hash_func(k);
498 }
499 std::hash<T> my_hash_func;
500 };
501
502 template <typename T, typename Allocator>
503 using container_type = tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>;
504
505 template <typename T>
506 using container_value_type = std::pair<const T, T>;
507
508 template<typename element_type, typename allocator_type>
509 struct apply {
510 using type = tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>;
511 };
512
513 using init_iterator_type = move_support_tests::FooPairIterator;
514 template <typename hash_map_type, typename iterator>
equalhash_map_traits515 static bool equal(hash_map_type const& c, iterator begin, iterator end){
516 bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() );
517 if (!equal_sizes)
518 return false;
519
520 for (iterator it = begin; it != end; ++it ){
521 if (c.count( (*it).first) == 0){
522 return false;
523 }
524 }
525 return true;
526 }
527 };
528
529 template <bool SimulateReacquiring>
530 class MinimalisticMutex {
531 public:
532 static constexpr bool is_rw_mutex = true;
533 static constexpr bool is_recursive_mutex = false;
534 static constexpr bool is_fair_mutex = false;
535
536 class scoped_lock {
537 public:
scoped_lock()538 constexpr scoped_lock() noexcept : my_mutex_ptr(nullptr) {}
539
scoped_lock(MinimalisticMutex & m,bool=true)540 scoped_lock( MinimalisticMutex& m, bool = true ) : my_mutex_ptr(&m) {
541 my_mutex_ptr->my_mutex.lock();
542 }
543
544 scoped_lock( const scoped_lock& ) = delete;
545 scoped_lock& operator=( const scoped_lock& ) = delete;
546
~scoped_lock()547 ~scoped_lock() {
548 if (my_mutex_ptr) release();
549 }
550
acquire(MinimalisticMutex & m,bool=true)551 void acquire( MinimalisticMutex& m, bool = true ) {
552 CHECK(my_mutex_ptr == nullptr);
553 my_mutex_ptr = &m;
554 my_mutex_ptr->my_mutex.lock();
555 }
556
try_acquire(MinimalisticMutex & m,bool=true)557 bool try_acquire( MinimalisticMutex& m, bool = true ) {
558 if (m.my_mutex.try_lock()) {
559 my_mutex_ptr = &m;
560 return true;
561 }
562 return false;
563 }
564
release()565 void release() {
566 CHECK(my_mutex_ptr != nullptr);
567 my_mutex_ptr->my_mutex.unlock();
568 my_mutex_ptr = nullptr;
569 }
570
upgrade_to_writer() const571 bool upgrade_to_writer() const {
572 // upgrade_to_writer should return false if the mutex simulates
573 // reacquiring the lock on upgrade operation
574 return !SimulateReacquiring;
575 }
576
downgrade_to_reader() const577 bool downgrade_to_reader() const {
578 // downgrade_to_reader should return false if the mutex simulates
579 // reacquiring the lock on upgrade operation
580 return !SimulateReacquiring;
581 }
582
is_writer() const583 bool is_writer() const {
584 CHECK(my_mutex_ptr != nullptr);
585 return true; // Always a writer
586 }
587
588 private:
589 MinimalisticMutex* my_mutex_ptr;
590 }; // class scoped_lock
591 private:
592 std::mutex my_mutex;
593 }; // class MinimalisticMutex
594
595 template <bool SimulateReacquiring>
test_with_minimalistic_mutex()596 void test_with_minimalistic_mutex() {
597 using mutex_type = MinimalisticMutex<SimulateReacquiring>;
598 using chmap_type = tbb::concurrent_hash_map<int, int, tbb::tbb_hash_compare<int>,
599 tbb::tbb_allocator<std::pair<const int, int>>,
600 mutex_type>;
601
602 chmap_type chmap;
603
604 // Insert pre-existing elements
605 for (int i = 0; i < 100; ++i) {
606 bool result = chmap.emplace(i, i);
607 CHECK(result);
608 }
609
610 // Insert elements to erase
611 for (int i = 10000; i < 10005; ++i) {
612 bool result = chmap.emplace(i, i);
613 CHECK(result);
614 }
615
616 auto thread_body = [&]( const tbb::blocked_range<std::size_t>& range ) {
617 for (std::size_t item = range.begin(); item != range.end(); ++item) {
618 switch(item % 4) {
619 case 0 :
620 // Insert new elements
621 for (int i = 100; i < 200; ++i) {
622 typename chmap_type::const_accessor acc;
623 chmap.emplace(acc, i, i);
624 CHECK(acc->first == i);
625 CHECK(acc->second == i);
626 }
627 break;
628 case 1 :
629 // Insert pre-existing elements
630 for (int i = 0; i < 100; ++i) {
631 typename chmap_type::const_accessor acc;
632 bool result = chmap.emplace(acc, i, i * 10000);
633 CHECK(!result);
634 CHECK(acc->first == i);
635 CHECK(acc->second == i);
636 }
637 break;
638 case 2 :
639 // Find pre-existing elements
640 for (int i = 0; i < 100; ++i) {
641 typename chmap_type::const_accessor acc;
642 bool result = chmap.find(acc, i);
643 CHECK(result);
644 CHECK(acc->first == i);
645 CHECK(acc->second == i);
646 }
647 break;
648 case 3 :
649 // Erase pre-existing elements
650 for (int i = 10000; i < 10005; ++i) {
651 chmap.erase(i);
652 }
653 break;
654 }
655 }
656 }; // thread_body
657
658 tbb::blocked_range<std::size_t> br(0, 1000, 8);
659
660 tbb::parallel_for(br, thread_body);
661
662 // Check pre-existing and new elements
663 for (int i = 0; i < 200; ++i) {
664 typename chmap_type::const_accessor acc;
665 bool result = chmap.find(acc, i);
666 REQUIRE_MESSAGE(result, "Some element was unexpectedly removed or not inserted");
667 REQUIRE_MESSAGE(acc->first == i, "Incorrect key");
668 REQUIRE_MESSAGE(acc->second == i, "Incorrect value");
669 }
670
671 // Check elements for erasure
672 for (int i = 10000; i < 10005; ++i) {
673 typename chmap_type::const_accessor acc;
674 bool result = chmap.find(acc, i);
675 REQUIRE_MESSAGE(!result, "Some element was not removed");
676 }
677 }
678
test_mutex_customization()679 void test_mutex_customization() {
680 test_with_minimalistic_mutex</*SimulateReacquiring = */false>();
681 test_with_minimalistic_mutex</*SimulateReacquiring = */true>();
682 }
683
684 struct SimpleTransparentHashCompare {
685 using is_transparent = void;
686
687 template <typename T>
hashSimpleTransparentHashCompare688 std::size_t hash(const T&) const { return 0; }
689
690 template <typename T, typename U>
equalSimpleTransparentHashCompare691 bool equal(const T& key1, const U& key2) const { return key1 == key2; }
692 };
693
694 template <typename Accessor>
695 struct IsWriterAccessor : public Accessor {
696 using Accessor::is_writer;
697 };
698
699 template <typename Map, typename Accessor>
test_chmap_access_mode(bool expect_write)700 void test_chmap_access_mode(bool expect_write) {
701 static_assert(std::is_same<int, typename Map::key_type>::value, "Incorrect test setup");
702 Map map;
703 Accessor acc;
704
705 // Test homogeneous insert
706 bool result = map.insert(acc, 1);
707 CHECK(result);
708 CHECK_MESSAGE(acc.is_writer() == expect_write, "Incorrect access into the map from homogeneous insert");
709
710 // Test heterogeneous insert
711 result = map.insert(acc, 2L);
712 CHECK(result);
713 CHECK_MESSAGE(acc.is_writer() == expect_write, "Incorrect access into the map from heterogeneous insert");
714
715 // Test lvalue insert
716 typename Map::value_type value{3, 3};
717 result = map.insert(acc, value);
718 CHECK(result);
719 CHECK_MESSAGE(acc.is_writer() == expect_write, "Incorrect access into the map from lvalue insert");
720
721 // Test rvalue insert
722 result = map.insert(acc, typename Map::value_type{4, 4});
723 CHECK(result);
724 CHECK_MESSAGE(acc.is_writer() == expect_write, "Incorrect access into the map from rvalue insert");
725
726 // Test homogeneous find
727 result = map.find(acc, 1);
728 CHECK(result);
729 CHECK_MESSAGE(acc.is_writer() == expect_write, "Incorrect access into the map from homogeneous find");
730
731 // Test heterogeneous find
732 result = map.find(acc, 2L);
733 CHECK(result);
734 CHECK_MESSAGE(acc.is_writer() == expect_write, "Incorrect access into the map from heterogeneous find");
735 }
736
737 //! Test of insert operation
738 //! \brief \ref error_guessing
739 TEST_CASE("testing range based for support"){
740 TestRangeBasedFor();
741 }
742
743 //! Test concurrent_hash_map with specific key/mapped types
744 //! \brief \ref regression \ref error_guessing
745 TEST_CASE("testing concurrent_hash_map with specific key/mapped types") {
746 TestSpecificTypes();
747 }
748
749 //! Test work with scoped allocator
750 //! \brief \ref regression
751 TEST_CASE("testing work with scoped allocator") {
752 TestScopedAllocator();
753 }
754
755 //! Test internal fast find for concurrent_hash_map
756 //! \brief \ref regression
757 TEST_CASE("testing internal fast find for concurrent_hash_map") {
758 TestInternalFastFind();
759 }
760
761 //! Test constructor with move iterators
762 //! \brief \ref error_guessing
763 TEST_CASE("testing constructor with move iterators"){
764 move_support_tests::test_constructor_with_move_iterators<hash_map_traits>();
765 }
766
767 #if TBB_USE_EXCEPTIONS
768 //! Test exception in constructors
769 //! \brief \ref regression \ref error_guessing
770 TEST_CASE("Test exception in constructors") {
771 using allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const int, int>>>;
772 using map_type = tbb::concurrent_hash_map<int, int, tbb::tbb_hash_compare<int>, allocator_type>;
773
774 auto init_list = {std::pair<const int, int>(1, 42), std::pair<const int, int>(2, 42), std::pair<const int, int>(3, 42),
775 std::pair<const int, int>(4, 42), std::pair<const int, int>(5, 42), std::pair<const int, int>(6, 42)};
776 map_type map(init_list);
777
778 allocator_type::set_limits(1);
__anon3741356d0302null779 REQUIRE_THROWS_AS( [&] {
780 map_type map1(map);
781 utils::suppress_unused_warning(map1);
782 }(), const std::bad_alloc);
783
__anon3741356d0402null784 REQUIRE_THROWS_AS( [&] {
785 map_type map2(init_list.begin(), init_list.end());
786 utils::suppress_unused_warning(map2);
787 }(), const std::bad_alloc);
788
789 tbb::tbb_hash_compare<int> test_hash;
790
__anon3741356d0502null791 REQUIRE_THROWS_AS( [&] {
792 map_type map3(init_list.begin(), init_list.end(), test_hash);
793 utils::suppress_unused_warning(map3);
794 }(), const std::bad_alloc);
795
__anon3741356d0602null796 REQUIRE_THROWS_AS( [&] {
797 map_type map4(init_list, test_hash);
798 utils::suppress_unused_warning(map4);
799 }(), const std::bad_alloc);
800
__anon3741356d0702null801 REQUIRE_THROWS_AS( [&] {
802 map_type map5(init_list);
803 utils::suppress_unused_warning(map5);
804 }(), const std::bad_alloc);
805
806 allocator_type::set_limits(0);
807 map_type big_map{};
808 for (int i = 0; i < 1000; ++i) {
809 big_map.insert(std::pair<const int, int>(i, 42));
810 }
811
812 allocator_type::init_counters();
813 allocator_type::set_limits(300);
__anon3741356d0802null814 REQUIRE_THROWS_AS( [&] {
815 map_type map6(big_map);
816 utils::suppress_unused_warning(map6);
817 }(), const std::bad_alloc);
818 }
819 #endif // TBB_USE_EXCEPTIONS
820
821 //! \brief \ref error_guessing
822 TEST_CASE("swap with NotAlwaysEqualAllocator allocators") {
823 using allocator_type = NotAlwaysEqualAllocator<std::pair<const int, int>>;
824 using map_type = tbb::concurrent_hash_map<int, int, tbb::tbb_hash_compare<int>, allocator_type>;
825
826 map_type map1{};
827 map_type map2({{42, 42}, {24, 42}});
828 map_type map3(map2);
829
830 swap(map1, map2);
831
832 CHECK(map2.empty());
833 CHECK(map1 == map3);
834 }
835
836 //! \brief \ref error_guessing
837 TEST_CASE("test concurrent_hash_map mutex customization") {
838 test_mutex_customization();
839 }
840
841 // A test for an issue when const_accessor passed to find provides write access into the map after the lookup
842 //! \brief \ref regression
843 TEST_CASE("test concurrent_hash_map accessors issue") {
844 using map_type = tbb::concurrent_hash_map<int, int, SimpleTransparentHashCompare>;
845 using accessor = IsWriterAccessor<typename map_type::accessor>;
846 using const_accessor = IsWriterAccessor<typename map_type::const_accessor>;
847
848 test_chmap_access_mode<map_type, accessor>(/*expect_write = */true);
849 test_chmap_access_mode<map_type, const_accessor>(/*expect_write = */false);
850 }
851
852 #if __TBB_CPP20_CONCEPTS_PRESENT
853 template <bool ExpectSatisfies, typename Key, typename Mapped, typename... HCTypes>
854 requires (... && (utils::well_formed_instantiation<tbb::concurrent_hash_map, Key, Mapped, HCTypes> == ExpectSatisfies))
test_chmap_hash_compare_constraints()855 void test_chmap_hash_compare_constraints() {}
856
857 //! \brief \ref error_guessing
858 TEST_CASE("tbb::concurrent_hash_map hash_compare constraints") {
859 using key_type = int;
860 using mapped_type = int;
861 using namespace test_concepts::hash_compare;
862
863 test_chmap_hash_compare_constraints</*Expected = */true, /*key = */key_type, /*mapped = */mapped_type,
864 Correct<key_type>, tbb::tbb_hash_compare<key_type>>();
865
866 test_chmap_hash_compare_constraints</*Expected = */false, /*key = */key_type, /*mapped = */mapped_type,
867 NonCopyable<key_type>, NonDestructible<key_type>,
868 NoHash<key_type>, HashNonConst<key_type>, WrongInputHash<key_type>, WrongReturnHash<key_type>,
869 NoEqual<key_type>, EqualNonConst<key_type>,
870 WrongFirstInputEqual<key_type>, WrongSecondInputEqual<key_type>, WrongReturnEqual<key_type>>();
871 }
872
873 template <bool ExpectSatisfies, typename Key, typename Mapped, typename... RWMutexes>
874 requires (... && (utils::well_formed_instantiation<tbb::concurrent_hash_map, Key, Mapped,
875 tbb::tbb_hash_compare<Key>, tbb::tbb_allocator<std::pair<const Key, Mapped>>, RWMutexes> == ExpectSatisfies))
test_chmap_mutex_constraints()876 void test_chmap_mutex_constraints() {}
877
878 //! \brief \ref error_guessing
879 TEST_CASE("tbb::concurrent_hash_map rw_mutex constraints") {
880 using key_type = int;
881 using mapped_type = int;
882 using namespace test_concepts::rw_mutex;
883
884 test_chmap_mutex_constraints</*Expected = */true, key_type, mapped_type,
885 Correct>();
886
887 test_chmap_mutex_constraints</*Expected = */false, key_type, mapped_type,
888 NoScopedLock, ScopedLockNoDefaultCtor, ScopedLockNoMutexCtor,
889 ScopedLockNoDtor, ScopedLockNoAcquire, ScopedLockWrongFirstInputAcquire, ScopedLockWrongSecondInputAcquire, ScopedLockNoTryAcquire,
890 ScopedLockWrongFirstInputTryAcquire, ScopedLockWrongSecondInputTryAcquire, ScopedLockWrongReturnTryAcquire, ScopedLockNoRelease,
891 ScopedLockNoUpgrade, ScopedLockWrongReturnUpgrade, ScopedLockNoDowngrade, ScopedLockWrongReturnDowngrade,
892 ScopedLockNoIsWriter, ScopedLockIsWriterNonConst, ScopedLockWrongReturnIsWriter>();
893 }
894
895 //! \brief \ref error_guessing
896 TEST_CASE("container_range concept for tbb::concurrent_hash_map ranges") {
897 static_assert(test_concepts::container_range<tbb::concurrent_hash_map<int, int>::range_type>);
898 static_assert(test_concepts::container_range<tbb::concurrent_hash_map<int, int>::const_range_type>);
899 }
900
901 #endif // __TBB_CPP20_CONCEPTS_PRESENT
902