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 // The test cannot work correctly with statically linked runtime.
234 // TODO: investigate a failure in debug with MSVC
235 #if !_MSC_VER || (defined(_DLL) && !defined(_DEBUG))
236 #include <csetjmp>
237 
238 // Overall, the test case is not safe because the dtors might not be called during long jump.
239 // Therefore, it makes sense to run the test case after all other test cases.
240 //! Test terminate_on_exception behavior
241 //! \brief \ref interface \ref requirement
242 TEST_CASE("terminate_on_exception: enabled") {
243     oneapi::tbb::global_control c(oneapi::tbb::global_control::terminate_on_exception, 1);
244     static bool terminate_handler_called;
245     terminate_handler_called = false;
246 
247 #if TBB_USE_EXCEPTIONS
248     try {
249 #endif
250         static std::jmp_buf buffer;
251         std::terminate_handler prev = std::set_terminate([] {
252             CHECK(!terminate_handler_called);
253             terminate_handler_called = true;
254             std::longjmp(buffer, 1);
255         });
256 #if _MSC_VER
257 #pragma warning(push)
258 #pragma warning(disable:4611) // interaction between '_setjmp' and C++ object destruction is non - portable
259 #endif
260         SUBCASE("internal exception") {
261             if (setjmp(buffer) == 0) {
262                 oneapi::tbb::parallel_for(0, 1, -1, [](int) {});
263                 FAIL("Unreachable code");
264             }
265         }
266 #if TBB_USE_EXCEPTIONS
267         SUBCASE("user exception") {
268             if (setjmp(buffer) == 0) {
269                 oneapi::tbb::parallel_for(0, 1, [](int) {
270                     volatile bool suppress_unreachable_code_warning = true;
271                     if (suppress_unreachable_code_warning) {
272                         throw std::exception();
273                     }
274                 });
275                 FAIL("Unreachable code");
276             }
277         }
278 #endif
279 #if _MSC_VER
280 #pragma warning(pop)
281 #endif
282         std::set_terminate(prev);
283         terminate_handler_called = true;
284 #if TBB_USE_EXCEPTIONS
285     } catch (...) {
286         FAIL("The exception is not expected");
287     }
288 #endif
289     CHECK(terminate_handler_called);
290 }
291 #endif
292 
293 //! Testing setting the same value but different objects
294 //! \brief \ref interface \ref error_guessing
295 TEST_CASE("setting same value") {
296     const std::size_t value = 2;
297 
298     oneapi::tbb::global_control* ctl1 = new oneapi::tbb::global_control(oneapi::tbb::global_control::max_allowed_parallelism, value);
299     oneapi::tbb::global_control* ctl2 = new oneapi::tbb::global_control(oneapi::tbb::global_control::max_allowed_parallelism, value);
300 
301     std::size_t active = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
302     REQUIRE(active == value);
303     delete ctl2;
304 
305     active = oneapi::tbb::global_control::active_value(oneapi::tbb::global_control::max_allowed_parallelism);
306     REQUIRE_MESSAGE(active == value, "Active value should not change, because of value duplication");
307     delete ctl1;
308 }
309