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