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 #include "common/test.h"
17 #include "common/concurrency_tracker.h"
18 #include "common/spin_barrier.h"
19 #include "common/utils.h"
20 #include "common/utils_concurrency_limit.h"
21
22 #include "oneapi/tbb/global_control.h"
23 #include "oneapi/tbb/parallel_for.h"
24
25 #include <limits.h>
26 #include <thread>
27
28 //! \file conformance_global_control.cpp
29 //! \brief Test for [sched.global_control] specification
30
31 const std::size_t MB = 1024*1024;
32
TestStackSizeSimpleControl()33 void TestStackSizeSimpleControl() {
34 oneapi::tbb::global_control s0(oneapi::tbb::global_control::thread_stack_size, 1*MB);
35
36 {
37 oneapi::tbb::global_control s1(oneapi::tbb::global_control::thread_stack_size, 8*MB);
38
39 CHECK(8*MB == oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size));
40 }
41 CHECK(1*MB == oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size));
42 }
43
44 struct StackSizeRun : utils::NoAssign {
45
46 int num_threads;
47 utils::SpinBarrier *barr1, *barr2;
48
StackSizeRunStackSizeRun49 StackSizeRun(int threads, utils::SpinBarrier *b1, utils::SpinBarrier *b2) :
50 num_threads(threads), barr1(b1), barr2(b2) {}
operator ()StackSizeRun51 void operator()( int id ) const {
52 oneapi::tbb::global_control s1(oneapi::tbb::global_control::thread_stack_size, (1+id)*MB);
53
54 barr1->wait();
55
56 REQUIRE(num_threads*MB == oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size));
57 barr2->wait();
58 }
59 };
60
TestStackSizeThreadsControl()61 void TestStackSizeThreadsControl() {
62 int threads = 4;
63 utils::SpinBarrier barr1(threads), barr2(threads);
64 utils::NativeParallelFor( threads, StackSizeRun(threads, &barr1, &barr2) );
65 }
66
RunWorkersLimited(size_t parallelism)67 void RunWorkersLimited(size_t parallelism)
68 {
69 oneapi::tbb::global_control s(oneapi::tbb::global_control::max_allowed_parallelism, parallelism);
70 // TODO: consider better testing approach
71 // Sleep is required because after destruction global_control on the previous iteration,
72 // it recalls the maximum concurrency and excessive worker threads might populate the arena.
73 // So, we need to wait when arena becomes empty but it is unreliable and might sporadically fail.
74 utils::Sleep(100);
75 const std::size_t expected_threads = (utils::get_platform_max_threads()==1)? 1 : parallelism;
76 utils::ExactConcurrencyLevel::check(expected_threads);
77 }
78
TestWorkersConstraints()79 void TestWorkersConstraints()
80 {
81 const size_t max_parallelism =
82 oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
83 if (max_parallelism > 3) {
84 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, max_parallelism-1);
85 CHECK_MESSAGE(max_parallelism-1 ==
86 oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism),
87 "Allowed parallelism must be decreasable.");
88 oneapi::tbb::global_control c1(oneapi::tbb::global_control::max_allowed_parallelism, max_parallelism-2);
89 CHECK_MESSAGE(max_parallelism-2 ==
90 oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism),
91 "Allowed parallelism must be decreasable.");
92 }
93 const size_t limit_par = utils::min(max_parallelism, 4U);
94 // check that constrains are really met
95 for (size_t num=2; num<limit_par; num++)
96 RunWorkersLimited(num);
97 for (size_t num=limit_par; num>1; num--)
98 RunWorkersLimited(num);
99 }
100
101 struct SetUseRun: utils::NoAssign {
102 utils::SpinBarrier &barr;
103
SetUseRunSetUseRun104 SetUseRun(utils::SpinBarrier& b) : barr(b) {}
operator ()SetUseRun105 void operator()( int id ) const {
106 if (id == 0) {
107 for (int i=0; i<10; i++) {
108 oneapi::tbb::parallel_for(0, 1000, utils::DummyBody(10), oneapi::tbb::simple_partitioner());
109 barr.wait();
110 }
111 } else {
112 for (int i=0; i<10; i++) {
113 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 8);
114 barr.wait();
115 }
116 }
117 }
118 };
119
TestConcurrentSetUseConcurrency()120 void TestConcurrentSetUseConcurrency()
121 {
122 utils::SpinBarrier barr(2);
123 NativeParallelFor( 2, SetUseRun(barr) );
124 }
125
126 // check number of workers after autoinitialization
TestAutoInit()127 void TestAutoInit()
128 {
129 const size_t max_parallelism =
130 oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
131 const unsigned expected_threads = utils::get_platform_max_threads()==1?
132 1 : (unsigned)max_parallelism;
133 utils::ExactConcurrencyLevel::check(expected_threads);
134 CHECK_MESSAGE(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism)
135 == max_parallelism, "max_allowed_parallelism must not be changed after auto init");
136 if (max_parallelism > 2) {
137 // after autoinit it's possible to decrease workers number
138 oneapi::tbb::global_control s(oneapi::tbb::global_control::max_allowed_parallelism, max_parallelism-1);
139 // TODO: consider better testing approach
140 // Sleep is required because after previous concurrency check, the arena is still populated with workers.
141 // So, we need to wait when arena becomes empty but it is unreliable and might sporadically fail.
142 utils::Sleep(100);
143 utils::ExactConcurrencyLevel::check(max_parallelism-1);
144 }
145 }
146
147 class TestMultipleControlsRun {
148 utils::SpinBarrier &barrier;
149 public:
TestMultipleControlsRun(utils::SpinBarrier & b)150 TestMultipleControlsRun(utils::SpinBarrier& b) : barrier(b) {}
operator ()(int id) const151 void operator()( int id ) const {
152 barrier.wait();
153 if (id) {
154 {
155 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 1);
156 utils::ExactConcurrencyLevel::check(1);
157 barrier.wait();
158 }
159 utils::ExactConcurrencyLevel::check(1);
160 barrier.wait();
161 {
162 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 2);
163 utils::ExactConcurrencyLevel::check(1);
164 barrier.wait();
165 utils::ExactConcurrencyLevel::check(2);
166 barrier.wait();
167 }
168 } else {
169 {
170 utils::ExactConcurrencyLevel::check(1);
171 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 1);
172 barrier.wait();
173 utils::ExactConcurrencyLevel::check(1);
174 barrier.wait();
175 utils::ExactConcurrencyLevel::check(1);
176 barrier.wait();
177 }
178 utils::ExactConcurrencyLevel::check(2);
179 barrier.wait();
180 }
181 }
182 };
183
184 // test that global controls from different thread with overlapping lifetime
185 // still keep parallelism under control
TestMultipleControls()186 void TestMultipleControls()
187 {
188 utils::SpinBarrier barrier(2);
189 utils::NativeParallelFor( 2, TestMultipleControlsRun(barrier) );
190 }
191
192 #if !(__TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00))
193 //! Testing setting stack size
194 //! \brief \ref interface \ref requirement
195 TEST_CASE("setting stack size") {
196 std::size_t default_ss = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size);
197 CHECK(default_ss > 0);
198 TestStackSizeSimpleControl();
199 TestStackSizeThreadsControl();
200 CHECK(default_ss == oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size));
201 }
202 #endif
203
204 //! Testing setting max number of threads
205 //! \brief \ref interface \ref requirement
206 TEST_CASE("setting max number of threads") {
207 TestWorkersConstraints();
208 }
209 //! Testing concurrenct setting concurrency
210 //! \brief \ref interface \ref requirement
211 TEST_CASE("concurrenct setting concurrency") {
212 TestConcurrentSetUseConcurrency();
213 }
214
215 //! Testing auto initialization
216 //! \brief \ref interface \ref requirement
217 TEST_CASE("auto initialization") {
218 TestAutoInit();
219 }
220
221 //! Test terminate_on_exception default value
222 //! \brief \ref interface \ref requirement
223 TEST_CASE("terminate_on_exception: default") {
224 std::size_t default_toe = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception);
225 CHECK(default_toe == 0);
226 }
227
228 //! Test terminate_on_exception in a nested case
229 //! \brief \ref interface \ref requirement
230 TEST_CASE("terminate_on_exception: nested") {
231 oneapi::tbb::global_control* c0;
232 {
233 oneapi::tbb::global_control c1(oneapi::tbb::global_control::terminate_on_exception, 1);
234 CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 1);
235 {
236 oneapi::tbb::global_control c2(oneapi::tbb::global_control::terminate_on_exception, 0);
237 CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 1);
238 }
239 CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 1);
240 c0 = new oneapi::tbb::global_control(oneapi::tbb::global_control::terminate_on_exception, 0);
241 }
242 CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 0);
243 delete c0;
244 }
245
246 //! Testing setting the same value but different objects
247 //! \brief \ref interface \ref error_guessing
248 TEST_CASE("setting same value") {
249 const std::size_t value = 2;
250
251 oneapi::tbb::global_control* ctl1 = new oneapi::tbb::global_control(oneapi::tbb::global_control::max_allowed_parallelism, value);
252 oneapi::tbb::global_control* ctl2 = new oneapi::tbb::global_control(oneapi::tbb::global_control::max_allowed_parallelism, value);
253
254 std::size_t active = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
255 REQUIRE(active == value);
256 delete ctl2;
257
258 active = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
259 REQUIRE_MESSAGE(active == value, "Active value should not change, because of value duplication");
260 delete ctl1;
261 }
262
263 //! Testing lifetime control conformance
264 //! \brief \ref interface \ref requirement
265 TEST_CASE("prolong lifetime simple") {
266 tbb::task_scheduler_handle hdl1{ tbb::attach{} };
267 {
268 tbb::parallel_for(0, 10, utils::DummyBody());
269
270 tbb::task_scheduler_handle hdl2;
271 hdl2 = tbb::task_scheduler_handle{ tbb::attach{} };
272 hdl2.release();
273 }
274 bool ok = tbb::finalize(hdl1, std::nothrow);
275 REQUIRE(ok);
276 }
277
278 //! Testing handle check for emptiness
279 //! \brief \ref interface \ref requirement
280 TEST_CASE("null handle check") {
281 tbb::task_scheduler_handle hndl;
282 REQUIRE_FALSE(hndl);
283 }
284
285 //! Testing handle check for emptiness
286 //! \brief \ref interface \ref requirement
287 TEST_CASE("null handle check 2") {
288 tbb::task_scheduler_handle hndl{ tbb::attach{} };
289 bool not_empty = (bool)hndl;
290
291 tbb::finalize(hndl, std::nothrow);
292
293 REQUIRE(not_empty);
294 REQUIRE_FALSE(hndl);
295 }
296
297 //! Testing handle check for emptiness
298 //! \brief \ref interface \ref requirement
299 TEST_CASE("null handle check 3") {
300 tbb::task_scheduler_handle handle1{ tbb::attach{} };
301 tbb::task_scheduler_handle handle2(std::move(handle1));
302
303 bool handle1_empty = !handle1;
304 bool handle2_not_empty = (bool)handle2;
305
306 tbb::finalize(handle2, std::nothrow);
307
308 REQUIRE(handle1_empty);
309 REQUIRE(handle2_not_empty);
310 }
311
312 //! Testing task_scheduler_handle is created on one thread and destroyed on another.
313 //! \brief \ref interface \ref requirement
314 TEST_CASE("cross thread 1") {
315 // created task_scheduler_handle, parallel_for on another thread - finalize
316 tbb::task_scheduler_handle handle{ tbb::attach{} };
__anon3ebcafb90102(int) 317 utils::NativeParallelFor(1, [&](int) {
318 tbb::parallel_for(0, 10, utils::DummyBody());
319 bool res = tbb::finalize(handle, std::nothrow);
320 REQUIRE(res);
321 });
322 }
323
324 //! Testing task_scheduler_handle is created on one thread and destroyed on another.
325 //! \brief \ref interface \ref requirement
326 TEST_CASE("cross thread 2") {
327 // created task_scheduler_handle, called parallel_for on this thread, killed the thread - and finalize on another thread
328 tbb::task_scheduler_handle handle;
__anon3ebcafb90202(int) 329 utils::NativeParallelFor(1, [&](int) {
330 handle = tbb::task_scheduler_handle{ tbb::attach{} };
331 tbb::parallel_for(0, 10, utils::DummyBody());
332 });
333 bool res = tbb::finalize(handle, std::nothrow);
334 REQUIRE(res);
335 }
336
337 //! Testing multiple wait
338 //! \brief \ref interface \ref requirement
339 TEST_CASE("simple prolong lifetime 3") {
340 // Parallel region
341 tbb::parallel_for(0, 10, utils::DummyBody());
342 // Termination
343 tbb::task_scheduler_handle handle = tbb::task_scheduler_handle{ tbb::attach{} };
344 bool res = tbb::finalize(handle, std::nothrow);
345 REQUIRE(res);
346 // New parallel region
347 tbb::parallel_for(0, 10, utils::DummyBody());
348 }
349
350 // The test cannot work correctly with statically linked runtime.
351 // TODO: investigate a failure in debug with MSVC
352 #if (!_MSC_VER || (defined(_DLL) && !defined(_DEBUG))) && !EMSCRIPTEN
353 #include <csetjmp>
354
355 // Overall, the test case is not safe because the dtors might not be called during long jump.
356 // Therefore, it makes sense to run the test case after all other test cases.
357 //! Test terminate_on_exception behavior
358 //! \brief \ref interface \ref requirement
359 TEST_CASE("terminate_on_exception: enabled") {
360 oneapi::tbb::global_control c(oneapi::tbb::global_control::terminate_on_exception, 1);
361 static bool terminate_handler_called;
362 terminate_handler_called = false;
363
364 #if TBB_USE_EXCEPTIONS
365 try {
366 #endif
367 static std::jmp_buf buffer;
__anon3ebcafb90302null368 std::terminate_handler prev = std::set_terminate([] {
369 CHECK(!terminate_handler_called);
370 terminate_handler_called = true;
371 std::longjmp(buffer, 1);
372 });
373 #if _MSC_VER
374 #pragma warning(push)
375 #pragma warning(disable:4611) // interaction between '_setjmp' and C++ object destruction is non - portable
376 #endif
377 SUBCASE("internal exception") {
378 if (setjmp(buffer) == 0) {
__anon3ebcafb90402(int) 379 oneapi::tbb::parallel_for(0, 1, -1, [](int) {});
380 FAIL("Unreachable code");
381 }
382 }
383 #if TBB_USE_EXCEPTIONS
384 SUBCASE("user exception") {
385 if (setjmp(buffer) == 0) {
__anon3ebcafb90502(int) 386 oneapi::tbb::parallel_for(0, 1, [](int) {
387 volatile bool suppress_unreachable_code_warning = true;
388 if (suppress_unreachable_code_warning) {
389 throw std::exception();
390 }
391 });
392 FAIL("Unreachable code");
393 }
394 }
395 #endif
396 #if _MSC_VER
397 #pragma warning(pop)
398 #endif
399 std::set_terminate(prev);
400 terminate_handler_called = true;
401 #if TBB_USE_EXCEPTIONS
402 } catch (...) {
403 FAIL("The exception is not expected");
404 }
405 #endif
406 CHECK(terminate_handler_called);
407 }
408 #endif
409