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 17 //! \file test_global_control.cpp 18 //! \brief Test for [sched.global_control] specification 19 20 #define TBB_PREVIEW_WAITING_FOR_WORKERS 1 21 22 #include "common/test.h" 23 24 #include "common/utils.h" 25 #include "common/spin_barrier.h" 26 #include "common/utils_concurrency_limit.h" 27 28 #include "tbb/global_control.h" 29 #include "tbb/parallel_for.h" 30 #include "tbb/task_group.h" 31 #include "tbb/task_arena.h" 32 33 #include <cstring> 34 35 struct task_scheduler_handle_guard { 36 tbb::task_scheduler_handle m_handle{}; 37 38 task_scheduler_handle_guard() { 39 m_handle = tbb::task_scheduler_handle::get(); 40 } 41 42 ~task_scheduler_handle_guard() { 43 tbb::task_scheduler_handle::release(m_handle); 44 } 45 46 tbb::task_scheduler_handle& get() { 47 return m_handle; 48 } 49 }; 50 51 namespace TestBlockingTerminateNS { 52 53 struct EmptyBody { 54 void operator()() const {} 55 void operator()( int ) const {} 56 }; 57 58 struct TestAutoInitBody { 59 void operator()( int ) const { 60 tbb::parallel_for( 0, 100, EmptyBody() ); 61 } 62 }; 63 64 static std::atomic<int> gSeed; 65 static std::atomic<int> gNumSuccesses; 66 67 class TestMultpleWaitBody { 68 bool myAutoInit; 69 public: 70 TestMultpleWaitBody( bool autoInit = false ) : myAutoInit( autoInit ) {} 71 void operator()( int ) const { 72 task_scheduler_handle_guard init; 73 if ( !myAutoInit ) { 74 tbb::parallel_for(0, 10, EmptyBody()); 75 } 76 utils::FastRandom<> rnd( ++gSeed ); 77 // In case of auto init sub-tests we skip 78 // - case #4 to avoid recursion 79 // - case #5 because it is explicit initialization 80 const int numCases = myAutoInit ? 4 : 6; 81 switch ( rnd.get() % numCases ) { 82 case 0: { 83 tbb::task_arena a; 84 a.enqueue( EmptyBody() ); 85 break; 86 } 87 case 1: { 88 tbb::task_group tg; 89 EmptyBody eb; 90 tg.run( eb ); 91 tg.wait(); 92 break; 93 } 94 case 2: 95 tbb::parallel_for( 0, 100, EmptyBody() ); 96 break; 97 case 3: 98 /* do nothing */ 99 break; 100 case 4: 101 // Create and join several threads with auto initialized scheduler. 102 utils::NativeParallelFor( rnd.get() % 5 + 1, TestMultpleWaitBody( true ) ); 103 break; 104 case 5: 105 { 106 task_scheduler_handle_guard init2; 107 bool res = tbb::finalize( init2.get(), std::nothrow ); 108 REQUIRE( !res ); 109 } 110 break; 111 } 112 if ( !myAutoInit && tbb::finalize( init.get(), std::nothrow ) ) 113 ++gNumSuccesses; 114 } 115 }; 116 117 void TestMultpleWait() { 118 const int minThreads = 1; 119 const int maxThreads = 16; 120 const int numRepeats = 5; 121 // Initialize seed with different values on different machines. 122 gSeed = tbb::this_task_arena::max_concurrency(); 123 for ( int repeats = 0; repeats<numRepeats; ++repeats ) { 124 for ( int threads = minThreads; threads<maxThreads; ++threads ) { 125 gNumSuccesses = 0; 126 utils::NativeParallelFor( threads, TestMultpleWaitBody() ); 127 REQUIRE_MESSAGE( gNumSuccesses > 0, "At least one blocking terminate must return 'true'" ); 128 } 129 } 130 } 131 132 #if TBB_USE_EXCEPTIONS 133 template <typename F> 134 void TestException( F &f ) { 135 utils::suppress_unused_warning( f ); 136 bool caught = false; 137 try { 138 f(); 139 REQUIRE( false ); 140 } 141 catch ( const tbb::unsafe_wait& e) { 142 const char* msg = e.what(); 143 REQUIRE((msg && std::strlen(msg) != 0)); 144 caught = true; 145 } 146 catch ( ... ) { 147 REQUIRE( false ); 148 } 149 REQUIRE( caught ); 150 } 151 152 class ExceptionTest1 { 153 task_scheduler_handle_guard tsi1; 154 int myIndex; 155 156 public: 157 ExceptionTest1( int index ) : myIndex( index ) {} 158 159 void operator()() { 160 task_scheduler_handle_guard tsi2; 161 tbb::parallel_for(0, 2, EmptyBody()); // auto-init 162 tbb::finalize((myIndex == 0 ? tsi1.get() : tsi2.get())); 163 REQUIRE_MESSAGE( false, "Blocking terminate did not throw the exception" ); 164 } 165 }; 166 167 struct ExceptionTest2 { 168 class Body { 169 utils::SpinBarrier& myBarrier; 170 public: 171 Body( utils::SpinBarrier& barrier ) : myBarrier( barrier ) {} 172 void operator()( int ) const { 173 myBarrier.wait(); 174 task_scheduler_handle_guard init; 175 tbb::finalize( init.get() ); 176 REQUIRE_MESSAGE( false, "Blocking terminate did not throw the exception inside the parallel region" ); 177 } 178 }; 179 void operator()() { 180 const int numThreads = 4; 181 tbb::global_control init(tbb::global_control::max_allowed_parallelism, numThreads); 182 tbb::task_arena a(numThreads); 183 a.execute([&] { 184 utils::SpinBarrier barrier(numThreads); 185 tbb::parallel_for(0, numThreads, Body(barrier)); 186 REQUIRE_MESSAGE(false, "Parallel loop did not throw the exception"); 187 }); 188 } 189 }; 190 191 void TestExceptions() { 192 ExceptionTest1 Test1(0); 193 TestException( Test1 ); 194 ExceptionTest1 Test2(1); 195 TestException( Test2 ); 196 if (utils::get_platform_max_threads() > 1) { 197 // TODO: Fix the arena leak issue on single threaded machine 198 // (see https://github.com/oneapi-src/oneTBB/issues/396) 199 ExceptionTest2 Test3; 200 TestException(Test3); 201 } 202 } 203 204 #endif /* TBB_USE_EXCEPTIONS */ 205 206 } // namespace TestBlockingTerminateNS 207 208 void TestTerminationAndAutoinit(bool autoinit) { 209 task_scheduler_handle_guard ctl1; 210 task_scheduler_handle_guard ctl2; 211 212 if (autoinit) { 213 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 214 } 215 bool res1 = tbb::finalize(ctl1.get(), std::nothrow); 216 if (autoinit) { 217 REQUIRE(!res1); 218 } else { 219 REQUIRE(res1); 220 } 221 bool res2 = tbb::finalize(ctl2.get(), std::nothrow); 222 REQUIRE(res2); 223 } 224 225 //! Check no reference leak for an external thread 226 //! \brief \ref regression \ref error_guessing 227 TEST_CASE("test decrease reference") { 228 tbb::task_scheduler_handle handle = tbb::task_scheduler_handle::get(); 229 230 std::thread thr([] { tbb::parallel_for(0, 1, [](int) {}); } ); 231 thr.join(); 232 233 REQUIRE(tbb::finalize(handle, std::nothrow)); 234 } 235 236 //! Testing lifetime control conformance 237 //! \brief \ref interface \ref requirement 238 TEST_CASE("prolong lifetime simple") { 239 tbb::task_scheduler_handle hdl1 = tbb::task_scheduler_handle::get(); 240 { 241 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 242 243 tbb::task_scheduler_handle hdl2 = tbb::task_scheduler_handle::get(); 244 tbb::task_scheduler_handle::release(hdl2); 245 } 246 bool ok = tbb::finalize(hdl1, std::nothrow); 247 REQUIRE(ok); 248 } 249 250 //! Testing lifetime control conformance 251 //! \brief \ref interface \ref requirement 252 TEST_CASE("prolong lifetime simple 2") { 253 TestTerminationAndAutoinit(false); 254 TestTerminationAndAutoinit(true); 255 } 256 257 //! Testing handle check for emptiness 258 //! \brief \ref interface \ref requirement 259 TEST_CASE("null handle check") { 260 tbb::task_scheduler_handle hndl; 261 REQUIRE_FALSE(hndl); 262 } 263 264 //! Testing handle check for emptiness 265 //! \brief \ref interface \ref requirement 266 TEST_CASE("null handle check 2") { 267 tbb::task_scheduler_handle hndl = tbb::task_scheduler_handle::get(); 268 bool not_empty = (bool)hndl; 269 270 tbb::finalize(hndl, std::nothrow); 271 272 REQUIRE(not_empty); 273 REQUIRE_FALSE(hndl); 274 } 275 276 //! Testing handle check for emptiness 277 //! \brief \ref interface \ref requirement 278 TEST_CASE("null handle check 3") { 279 tbb::task_scheduler_handle handle1 = tbb::task_scheduler_handle::get(); 280 tbb::task_scheduler_handle handle2(std::move(handle1)); 281 282 bool handle1_empty = !handle1; 283 bool handle2_not_empty = (bool)handle2; 284 285 tbb::finalize(handle2, std::nothrow); 286 287 REQUIRE(handle1_empty); 288 REQUIRE(handle2_not_empty); 289 } 290 291 #if TBB_USE_EXCEPTIONS 292 //! Testing lifetime control advanced 293 //! \brief \ref interface \ref requirement 294 TEST_CASE("prolong lifetime advanced") { 295 // Exceptions test leaves auto-initialized sheduler after, 296 // because all blocking terminate calls are inside the parallel region, 297 // thus resulting in false termination result. 298 utils::NativeParallelFor(1, 299 [&](int) { TestBlockingTerminateNS::TestExceptions(); }); 300 } 301 #endif 302 303 //! Testing multiple wait 304 //! \brief \ref interface \ref requirement 305 TEST_CASE("prolong lifetime multiple wait") { 306 TestBlockingTerminateNS::TestMultpleWait(); 307 } 308 309 //! Testing global_control is created on one thread and destroyed on another. 310 //! \brief \ref interface \ref requirement 311 TEST_CASE("cross thread 1") { 312 // created GC, parallel_for on another thread - finalize 313 tbb::task_scheduler_handle ctl = tbb::task_scheduler_handle::get(); 314 utils::NativeParallelFor(1, [&](int) { 315 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 316 bool res = tbb::finalize(ctl, std::nothrow); 317 REQUIRE(res); 318 }); 319 } 320 321 //! Testing global_control is created on one thread and destroyed on another. 322 //! \brief \ref interface \ref requirement 323 TEST_CASE("cross thread 2") { 324 // created GC, called parallel_for on this thread, killed the thread - and finalize on another thread 325 tbb::task_scheduler_handle ctl; 326 utils::NativeParallelFor(1, [&](int) { 327 ctl = tbb::task_scheduler_handle::get(); 328 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 329 }); 330 bool res = tbb::finalize(ctl, std::nothrow); 331 REQUIRE(res); 332 } 333 334 //! Testing multiple wait 335 //! \brief \ref interface \ref requirement 336 TEST_CASE("simple prolong lifetime 3") { 337 // Parallel region 338 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 339 // Termination 340 tbb::task_scheduler_handle ctl = tbb::task_scheduler_handle::get(); 341 bool res = tbb::finalize(ctl, std::nothrow); 342 REQUIRE(res); 343 // New parallel region 344 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 345 } 346 347