1 /*
2     Copyright (c) 2005-2021 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 
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 
49     StackSizeRun(int threads, utils::SpinBarrier *b1, utils::SpinBarrier *b2) :
50         num_threads(threads), barr1(b1), barr2(b2) {}
51     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 
61 void TestStackSizeThreadsControl() {
62     int threads = 4;
63     utils::SpinBarrier barr1(threads), barr2(threads);
64     utils::NativeParallelFor( threads, StackSizeRun(threads, &barr1, &barr2) );
65 }
66 
67 void RunWorkersLimited(size_t parallelism, bool wait)
68 {
69     oneapi::tbb::global_control s(oneapi::tbb::global_control::max_allowed_parallelism, parallelism);
70     // try both configuration with already sleeping workers and with not yet sleeping
71     if (wait)
72         utils::Sleep(10);
73     const std::size_t expected_threads = (utils::get_platform_max_threads()==1)? 1 : parallelism;
74     utils::ExactConcurrencyLevel::check(expected_threads);
75 }
76 
77 void TestWorkersConstraints()
78 {
79     const size_t max_parallelism =
80         oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
81     if (max_parallelism > 3) {
82         oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, max_parallelism-1);
83         CHECK_MESSAGE(max_parallelism-1 ==
84                oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism),
85                "Allowed parallelism must be decreasable.");
86         oneapi::tbb::global_control c1(oneapi::tbb::global_control::max_allowed_parallelism, max_parallelism-2);
87         CHECK_MESSAGE(max_parallelism-2 ==
88                oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism),
89                "Allowed parallelism must be decreasable.");
90     }
91     const size_t limit_par = utils::min(max_parallelism, 4U);
92     // check that constrains are really met
93     for (int wait=0; wait<2; wait++) {
94         for (size_t num=2; num<limit_par; num++)
95             RunWorkersLimited(num, wait==1);
96         for (size_t num=limit_par; num>1; num--)
97             RunWorkersLimited(num, wait==1);
98     }
99 }
100 
101 struct SetUseRun: utils::NoAssign {
102     utils::SpinBarrier &barr;
103 
104     SetUseRun(utils::SpinBarrier& b) : barr(b) {}
105     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 
120 void TestConcurrentSetUseConcurrency()
121 {
122     utils::SpinBarrier barr(2);
123     NativeParallelFor( 2, SetUseRun(barr) );
124 }
125 
126 // check number of workers after autoinitialization
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         utils::ExactConcurrencyLevel::check(max_parallelism-1);
140     }
141 }
142 
143 class TestMultipleControlsRun {
144     utils::SpinBarrier &barrier;
145 public:
146     TestMultipleControlsRun(utils::SpinBarrier& b) : barrier(b) {}
147     void operator()( int id ) const {
148         barrier.wait();
149         if (id) {
150             {
151                 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 1);
152                 utils::ExactConcurrencyLevel::check(1);
153                 barrier.wait();
154             }
155             utils::ExactConcurrencyLevel::check(1);
156             barrier.wait();
157             {
158                 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 2);
159                 utils::ExactConcurrencyLevel::check(1);
160                 barrier.wait();
161                 utils::ExactConcurrencyLevel::check(2);
162                 barrier.wait();
163             }
164         } else {
165             {
166                 utils::ExactConcurrencyLevel::check(1);
167                 oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, 1);
168                 barrier.wait();
169                 utils::ExactConcurrencyLevel::check(1);
170                 barrier.wait();
171                 utils::ExactConcurrencyLevel::check(1);
172                 barrier.wait();
173             }
174             utils::ExactConcurrencyLevel::check(2);
175             barrier.wait();
176         }
177     }
178 };
179 
180 // test that global controls from different thread with overlapping lifetime
181 // still keep parallelism under control
182 void TestMultipleControls()
183 {
184     utils::SpinBarrier barrier(2);
185     utils::NativeParallelFor( 2, TestMultipleControlsRun(barrier) );
186 }
187 
188 #if !(__TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00))
189 //! Testing setting stack size
190 //! \brief \ref interface \ref requirement
191 TEST_CASE("setting stack size") {
192     std::size_t default_ss = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size);
193     CHECK(default_ss > 0);
194     TestStackSizeSimpleControl();
195     TestStackSizeThreadsControl();
196     CHECK(default_ss == oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::thread_stack_size));
197 }
198 #endif
199 
200 //! Testing setting max number of threads
201 //! \brief \ref interface \ref requirement
202 TEST_CASE("setting max number of threads") {
203     TestWorkersConstraints();
204     TestConcurrentSetUseConcurrency();
205     TestAutoInit();
206 }
207 
208 //! Test terminate_on_exception default value
209 //! \brief \ref interface \ref requirement
210 TEST_CASE("terminate_on_exception: default") {
211     std::size_t default_toe = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception);
212     CHECK(default_toe == 0);
213 }
214 
215 //! Test terminate_on_exception in a nested case
216 //! \brief \ref interface \ref requirement
217 TEST_CASE("terminate_on_exception: nested") {
218     oneapi::tbb::global_control* c0;
219     {
220         oneapi::tbb::global_control c1(oneapi::tbb::global_control::terminate_on_exception, 1);
221         CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 1);
222         {
223             oneapi::tbb::global_control c2(oneapi::tbb::global_control::terminate_on_exception, 0);
224             CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 1);
225         }
226         CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 1);
227         c0 = new oneapi::tbb::global_control(oneapi::tbb::global_control::terminate_on_exception, 0);
228     }
229     CHECK(oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::terminate_on_exception) == 0);
230     delete c0;
231 }
232 
233 //! Testing setting the same value but different objects
234 //! \brief \ref interface \ref error_guessing
235 TEST_CASE("setting same value") {
236     const std::size_t value = 2;
237 
238     oneapi::tbb::global_control* ctl1 = new oneapi::tbb::global_control(oneapi::tbb::global_control::max_allowed_parallelism, value);
239     oneapi::tbb::global_control* ctl2 = new oneapi::tbb::global_control(oneapi::tbb::global_control::max_allowed_parallelism, value);
240 
241     std::size_t active = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
242     REQUIRE(active == value);
243     delete ctl2;
244 
245     active = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
246     REQUIRE_MESSAGE(active == value, "Active value should not change, because of value duplication");
247     delete ctl1;
248 }
249 
250 //! Testing lifetime control conformance
251 //! \brief \ref interface \ref requirement
252 TEST_CASE("prolong lifetime simple") {
253     tbb::task_scheduler_handle hdl1{ tbb::attach{} };
254     {
255         tbb::parallel_for(0, 10, utils::DummyBody());
256 
257         tbb::task_scheduler_handle hdl2;
258         hdl2 = tbb::task_scheduler_handle{ tbb::attach{} };
259         hdl2.release();
260     }
261     bool ok = tbb::finalize(hdl1, std::nothrow);
262     REQUIRE(ok);
263 }
264 
265 //! Testing handle check for emptiness
266 //! \brief \ref interface \ref requirement
267 TEST_CASE("null handle check") {
268     tbb::task_scheduler_handle hndl;
269     REQUIRE_FALSE(hndl);
270 }
271 
272 //! Testing handle check for emptiness
273 //! \brief \ref interface \ref requirement
274 TEST_CASE("null handle check 2") {
275     tbb::task_scheduler_handle hndl{ tbb::attach{} };
276     bool not_empty = (bool)hndl;
277 
278     tbb::finalize(hndl, std::nothrow);
279 
280     REQUIRE(not_empty);
281     REQUIRE_FALSE(hndl);
282 }
283 
284 //! Testing handle check for emptiness
285 //! \brief \ref interface \ref requirement
286 TEST_CASE("null handle check 3") {
287     tbb::task_scheduler_handle handle1{ tbb::attach{} };
288     tbb::task_scheduler_handle handle2(std::move(handle1));
289 
290     bool handle1_empty = !handle1;
291     bool handle2_not_empty = (bool)handle2;
292 
293     tbb::finalize(handle2, std::nothrow);
294 
295     REQUIRE(handle1_empty);
296     REQUIRE(handle2_not_empty);
297 }
298 
299 //! Testing  task_scheduler_handle is created on one thread and destroyed on another.
300 //! \brief \ref interface \ref requirement
301 TEST_CASE("cross thread 1") {
302     // created task_scheduler_handle, parallel_for on another thread - finalize
303     tbb::task_scheduler_handle handle{ tbb::attach{} };
304     utils::NativeParallelFor(1, [&](int) {
305         tbb::parallel_for(0, 10, utils::DummyBody());
306         bool res = tbb::finalize(handle, std::nothrow);
307         REQUIRE(res);
308     });
309 }
310 
311 //! Testing  task_scheduler_handle is created on one thread and destroyed on another.
312 //! \brief \ref interface \ref requirement
313 TEST_CASE("cross thread 2") {
314     // created task_scheduler_handle, called parallel_for on this thread, killed the thread - and finalize on another thread
315     tbb::task_scheduler_handle handle;
316     utils::NativeParallelFor(1, [&](int) {
317         handle = tbb::task_scheduler_handle{ tbb::attach{} };
318         tbb::parallel_for(0, 10, utils::DummyBody());
319     });
320     bool res = tbb::finalize(handle, std::nothrow);
321     REQUIRE(res);
322 }
323 
324 //! Testing multiple wait
325 //! \brief \ref interface \ref requirement
326 TEST_CASE("simple prolong lifetime 3") {
327     // Parallel region
328     tbb::parallel_for(0, 10, utils::DummyBody());
329     // Termination
330     tbb::task_scheduler_handle handle = tbb::task_scheduler_handle{ tbb::attach{} };
331     bool res = tbb::finalize(handle, std::nothrow);
332     REQUIRE(res);
333     // New parallel region
334     tbb::parallel_for(0, 10, utils::DummyBody());
335 }
336 
337 // The test cannot work correctly with statically linked runtime.
338 // TODO: investigate a failure in debug with MSVC
339 #if !_MSC_VER || (defined(_DLL) && !defined(_DEBUG))
340 #include <csetjmp>
341 
342 // Overall, the test case is not safe because the dtors might not be called during long jump.
343 // Therefore, it makes sense to run the test case after all other test cases.
344 //! Test terminate_on_exception behavior
345 //! \brief \ref interface \ref requirement
346 TEST_CASE("terminate_on_exception: enabled") {
347     oneapi::tbb::global_control c(oneapi::tbb::global_control::terminate_on_exception, 1);
348     static bool terminate_handler_called;
349     terminate_handler_called = false;
350 
351 #if TBB_USE_EXCEPTIONS
352     try {
353 #endif
354         static std::jmp_buf buffer;
355         std::terminate_handler prev = std::set_terminate([] {
356             CHECK(!terminate_handler_called);
357             terminate_handler_called = true;
358             std::longjmp(buffer, 1);
359         });
360 #if _MSC_VER
361 #pragma warning(push)
362 #pragma warning(disable:4611) // interaction between '_setjmp' and C++ object destruction is non - portable
363 #endif
364         SUBCASE("internal exception") {
365             if (setjmp(buffer) == 0) {
366                 oneapi::tbb::parallel_for(0, 1, -1, [](int) {});
367                 FAIL("Unreachable code");
368             }
369         }
370 #if TBB_USE_EXCEPTIONS
371         SUBCASE("user exception") {
372             if (setjmp(buffer) == 0) {
373                 oneapi::tbb::parallel_for(0, 1, [](int) {
374                     volatile bool suppress_unreachable_code_warning = true;
375                     if (suppress_unreachable_code_warning) {
376                         throw std::exception();
377                     }
378                 });
379                 FAIL("Unreachable code");
380             }
381         }
382 #endif
383 #if _MSC_VER
384 #pragma warning(pop)
385 #endif
386         std::set_terminate(prev);
387         terminate_handler_called = true;
388 #if TBB_USE_EXCEPTIONS
389     } catch (...) {
390         FAIL("The exception is not expected");
391     }
392 #endif
393     CHECK(terminate_handler_called);
394 }
395 #endif
396