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 #include <common/test.h>
18 #include <common/utils.h>
19 #include <common/vector_types.h>
20 #include <common/custom_allocators.h>
21
22 #include <tbb/concurrent_queue.h>
23 #include <unordered_set>
24
25 //! \file test_concurrent_queue.cpp
26 //! \brief Test for [containers.concurrent_queue containers.concurrent_bounded_queue] specification
27
28 static constexpr std::size_t MaxThread = 4;
29
30 template<typename CQ, typename T>
31 struct TestQueueElements {
32 CQ& queue;
33 const std::size_t nthread;
TestQueueElementsTestQueueElements34 TestQueueElements( CQ& q, std::size_t n ) : queue(q), nthread(n) {}
operator ()TestQueueElements35 void operator()( std::size_t k ) const {
36 for (std::size_t i=0; i < 1000; ++i) {
37 if( (i&0x1)==0 ) {
38 CHECK(T(k) < T(nthread));
39 queue.push(T(k));
40 } else {
41 // Pop item from queue
42 T item = 0;
43 queue.try_pop(item);
44 CHECK(item <= T(nthread));
45 }
46 }
47 }
48 };
49
50 //! Test concurrent queue with primitive data type
51 template<typename CQ, typename T>
TestPrimitiveTypes(std::size_t nthread,T exemplar)52 void TestPrimitiveTypes(std::size_t nthread, T exemplar) {
53 CQ queue;
54 for (std::size_t i = 0; i < 100; ++i) {
55 queue.push(exemplar);
56 }
57 utils::NativeParallelFor(nthread, TestQueueElements<CQ, T>(queue, nthread));
58 }
59
TestQueueWorksWithPrimitiveTypes()60 void TestQueueWorksWithPrimitiveTypes() {
61 TestPrimitiveTypes<tbb::concurrent_queue<char>, char>(MaxThread, (char)1);
62 TestPrimitiveTypes<tbb::concurrent_queue<int>, int>(MaxThread, (int)-12);
63 TestPrimitiveTypes<tbb::concurrent_queue<float>, float>(MaxThread, (float)-1.2f);
64 TestPrimitiveTypes<tbb::concurrent_queue<double>, double>(MaxThread, (double)-4.3);
65 TestPrimitiveTypes<tbb::concurrent_bounded_queue<char>, char>(MaxThread, (char)1);
66 TestPrimitiveTypes<tbb::concurrent_bounded_queue<int>, int>(MaxThread, (int)-12);
67 TestPrimitiveTypes<tbb::concurrent_bounded_queue<float>, float>(MaxThread, (float)-1.2f);
68 TestPrimitiveTypes<tbb::concurrent_bounded_queue<double>, double>(MaxThread, (double)-4.3);
69 }
70
71 #if HAVE_m128 || HAVE_m256
72 //! Test concurrent queue with vector types
73 /** Type Queue should be a queue of ClassWithSSE/ClassWithAVX. */
74 template<typename ClassWithVectorType, typename Queue>
TestVectorTypes()75 void TestVectorTypes() {
76 Queue q1;
77 for (int i = 0; i < 100; ++i) {
78 // VC8 does not properly align a temporary value; to work around, use explicit variable
79 ClassWithVectorType bar(i);
80 q1.push(bar);
81 }
82
83 // Copy the queue
84 Queue q2 = q1;
85 // Check that elements of the copy are correct
86 typename Queue::const_iterator ci = q2.unsafe_begin();
87 for (int i=0; i < 100; ++i ) {
88 CHECK((ci != q2.unsafe_end()));
89 ClassWithVectorType foo = *ci;
90 ClassWithVectorType bar(i);
91 CHECK((*ci == bar));
92 ++ci;
93 }
94
95 for (int i = 0; i < 101; ++i) {
96 ClassWithVectorType tmp;
97 bool b = q1.try_pop(tmp);
98 CHECK((b == (i < 100)));
99 ClassWithVectorType bar(i);
100 CHECK((!b || tmp==bar));
101 }
102 }
103 #endif /* HAVE_m128 || HAVE_m256 */
104
TestQueueWorksWithSSE()105 void TestQueueWorksWithSSE() {
106
107 #if HAVE_m128
108 TestVectorTypes<ClassWithSSE, tbb::concurrent_queue<ClassWithSSE> >();
109 TestVectorTypes<ClassWithSSE, tbb::concurrent_bounded_queue<ClassWithSSE> >();
110 #endif /* HAVE_m128 */
111 #if HAVE_m256
112 if( have_AVX() ) {
113 TestVectorTypes<ClassWithAVX, tbb::concurrent_queue<ClassWithAVX> >();
114 TestVectorTypes<ClassWithAVX, tbb::concurrent_bounded_queue<ClassWithAVX> >();
115 }
116 #endif /* HAVE_m256 */
117 }
118 #if TBB_USE_EXCEPTIONS
119 int rnd_elem = -1;
120 int global_counter = -1;
121
122 struct throw_element {
123 throw_element() = default;
throw_elementthrow_element124 throw_element(const throw_element&) {
125 if (global_counter++ == rnd_elem) {
126 throw std::exception{};
127 }
128 }
129
130 throw_element& operator= (const throw_element&) = default;
131 };
132
133 template <typename Queue>
CopyWithThrowElement()134 void CopyWithThrowElement() {
135 utils::FastRandom<> rnd(42);
136
137 Queue source;
138
139 constexpr size_t queue_size = 100000;
140 for (std::size_t i = 0; i < queue_size; ++i) {
141 source.emplace();
142 }
143
144 for (std::size_t i = 0; i < 100; ++i) {
145 global_counter = 0;
146 rnd_elem = rnd.get() % queue_size;
147
148 REQUIRE_THROWS_AS( [&] {
149 Queue copy(source);
150 utils::suppress_unused_warning(copy);
151 }(), std::exception);
152 }
153 }
154 #endif // TBB_USE_EXCEPTIONS
155
156 //! Test work with different fypes
157 //! \brief \ref error_guessing
158 TEST_CASE("testing work with different fypes") {
159 TestQueueWorksWithPrimitiveTypes();
160 }
161
162 //! Test work with vector types
163 //! \brief \ref error_guessing
164 TEST_CASE("testing vector types") {
165 TestQueueWorksWithSSE();
166 }
167
168 #if TBB_USE_EXCEPTIONS
169 //! \brief \ref regression \ref error_guessing
170 TEST_CASE("Test exception in allocation") {
171 using allocator_type = StaticSharedCountingAllocator<std::allocator<int>>;
172 using queue_type = tbb::concurrent_queue<int, allocator_type>;
173
174 queue_type src_queue;
175 for (int i = 0; i < 100000; ++i) {
176 src_queue.push(i);
177 }
178
179 allocator_type::set_limits(1);
180
__anon525151d10202null181 REQUIRE_THROWS_AS( [] {
182 queue_type queue1;
183 queue1.push(1);
184 }(), const std::bad_alloc);
185
186 for (std::size_t i = 1; i < 1000; ++i) {
187 allocator_type::init_counters();
188 allocator_type::set_limits(1);
__anon525151d10302null189 REQUIRE_THROWS_AS( [&] {
190 queue_type queue2(src_queue);
191 utils::suppress_unused_warning(queue2);
192 }(), const std::bad_alloc);
193 }
194 }
195
196 //! \brief \ref regression \ref error_guessing
197 TEST_CASE("Test exception in allocation") {
198 CopyWithThrowElement<tbb::concurrent_queue<throw_element>>();
199 CopyWithThrowElement<tbb::concurrent_bounded_queue<throw_element>>();
200 }
201
202 #endif // TBB_USE_EXCEPTIONS
203
204 struct TrackableItem {
205 static std::unordered_set<TrackableItem*> object_addresses;
206 #if TBB_USE_EXCEPTIONS
207 static std::size_t global_count_for_exceptions;
208 #endif
209
TrackableItemTrackableItem210 TrackableItem() {
211 #if TBB_USE_EXCEPTIONS
212 if (global_count_for_exceptions++ % 3 == 0) throw 1;
213 #endif
214 bool res = object_addresses.emplace(this).second;
215 CHECK(res);
216 }
217
~TrackableItemTrackableItem218 ~TrackableItem() {
219 auto it = object_addresses.find(this);
220 CHECK(it != object_addresses.end());
221 object_addresses.erase(it);
222 CHECK(object_addresses.count(this) == 0);
223 }
224 };
225
226 template <typename Container>
fill_and_catch(Container & q,std::size_t elements_count)227 void fill_and_catch(Container& q, std::size_t elements_count) {
228 CHECK(TrackableItem::object_addresses.size() == 0);
229 for (std::size_t i = 0; i < elements_count; ++i) {
230 #if TBB_USE_EXCEPTIONS
231 try {
232 #endif
233 q.emplace();
234 #if TBB_USE_EXCEPTIONS
235 } catch (int exception) {
236 CHECK(exception == 1);
237 }
238 #endif
239 }
240 #if TBB_USE_EXCEPTIONS
241 CHECK(TrackableItem::object_addresses.size() == 2 * elements_count / 3);
242 #else
243 CHECK(TrackableItem::object_addresses.size() == elements_count);
244 #endif
245 }
246
247 std::unordered_set<TrackableItem*> TrackableItem::object_addresses;
248 #if TBB_USE_EXCEPTIONS
249 std::size_t TrackableItem::global_count_for_exceptions = 0;
250 #endif
251
252 template <typename Container>
test_tracking_dtors_on_clear()253 void test_tracking_dtors_on_clear() {
254 static_assert(std::is_same<typename Container::value_type, TrackableItem>::value, "Incorrect test setup");
255 const std::size_t elements_count = 100000;
256 {
257 Container q;
258 fill_and_catch(q, elements_count);
259
260 q.clear();
261
262 CHECK(q.empty());
263 CHECK(TrackableItem::object_addresses.empty());
264 #if TBB_USE_EXCEPTIONS
265 TrackableItem::global_count_for_exceptions = 0;
266 #endif
267 }
268 {
269 {
270 Container q;
271 fill_and_catch(q, elements_count);
272 } // Dtor of q would be called here
273 CHECK(TrackableItem::object_addresses.empty());
274 #if TBB_USE_EXCEPTIONS
275 TrackableItem::global_count_for_exceptions = 0;
276 #endif
277 }
278 }
279
280 //! \brief \ref regression \ref error_guessing
281 TEST_CASE("Test clear and dtor with TrackableItem") {
282 test_tracking_dtors_on_clear<oneapi::tbb::concurrent_queue<TrackableItem>>();
283 test_tracking_dtors_on_clear<oneapi::tbb::concurrent_bounded_queue<TrackableItem>>();
284 }
285