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