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 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) 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 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 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 // 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: 150 TestMultipleControlsRun(utils::SpinBarrier& b) : barrier(b) {} 151 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 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{} }; 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; 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)) 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; 368 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) { 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) { 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