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 //! Testing lifetime control conformance 226 //! \brief \ref interface \ref requirement 227 TEST_CASE("prolong lifetime simple") { 228 tbb::task_scheduler_handle hdl1 = tbb::task_scheduler_handle::get(); 229 { 230 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 231 232 tbb::task_scheduler_handle hdl2 = tbb::task_scheduler_handle::get(); 233 tbb::task_scheduler_handle::release(hdl2); 234 } 235 bool ok = tbb::finalize(hdl1, std::nothrow); 236 REQUIRE(ok); 237 } 238 239 //! Testing lifetime control conformance 240 //! \brief \ref interface \ref requirement 241 TEST_CASE("prolong lifetime simple 2") { 242 TestTerminationAndAutoinit(false); 243 TestTerminationAndAutoinit(true); 244 } 245 246 //! Testing handle check for emptiness 247 //! \brief \ref interface \ref requirement 248 TEST_CASE("null handle check") { 249 tbb::task_scheduler_handle hndl; 250 REQUIRE_FALSE(hndl); 251 } 252 253 //! Testing handle check for emptiness 254 //! \brief \ref interface \ref requirement 255 TEST_CASE("null handle check 2") { 256 tbb::task_scheduler_handle hndl = tbb::task_scheduler_handle::get(); 257 bool not_empty = (bool)hndl; 258 259 tbb::finalize(hndl, std::nothrow); 260 261 REQUIRE(not_empty); 262 REQUIRE_FALSE(hndl); 263 } 264 265 //! Testing handle check for emptiness 266 //! \brief \ref interface \ref requirement 267 TEST_CASE("null handle check 3") { 268 tbb::task_scheduler_handle handle1 = tbb::task_scheduler_handle::get(); 269 tbb::task_scheduler_handle handle2(std::move(handle1)); 270 271 bool handle1_empty = !handle1; 272 bool handle2_not_empty = (bool)handle2; 273 274 tbb::finalize(handle2, std::nothrow); 275 276 REQUIRE(handle1_empty); 277 REQUIRE(handle2_not_empty); 278 } 279 280 #if TBB_USE_EXCEPTIONS 281 //! Testing lifetime control advanced 282 //! \brief \ref interface \ref requirement 283 TEST_CASE("prolong lifetime advanced") { 284 // Exceptions test leaves auto-initialized sheduler after, 285 // because all blocking terminate calls are inside the parallel region, 286 // thus resulting in false termination result. 287 utils::NativeParallelFor(1, 288 [&](int) { TestBlockingTerminateNS::TestExceptions(); }); 289 } 290 #endif 291 292 //! Testing multiple wait 293 //! \brief \ref interface \ref requirement 294 TEST_CASE("prolong lifetime multiple wait") { 295 TestBlockingTerminateNS::TestMultpleWait(); 296 } 297 298 //! Testing global_control is created on one thread and destroyed on another. 299 //! \brief \ref interface \ref requirement 300 TEST_CASE("cross thread 1") { 301 // created GC, parallel_for on another thread - finalize 302 tbb::task_scheduler_handle ctl = tbb::task_scheduler_handle::get(); 303 utils::NativeParallelFor(1, [&](int) { 304 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 305 bool res = tbb::finalize(ctl, std::nothrow); 306 REQUIRE(res); 307 }); 308 } 309 310 //! Testing global_control is created on one thread and destroyed on another. 311 //! \brief \ref interface \ref requirement 312 TEST_CASE("cross thread 2") { 313 // created GC, called parallel_for on this thread, killed the thread - and finalize on another thread 314 tbb::task_scheduler_handle ctl; 315 utils::NativeParallelFor(1, [&](int) { 316 ctl = tbb::task_scheduler_handle::get(); 317 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 318 }); 319 bool res = tbb::finalize(ctl, std::nothrow); 320 REQUIRE(res); 321 } 322 323 //! Testing multiple wait 324 //! \brief \ref interface \ref requirement 325 TEST_CASE("simple prolong lifetime 3") { 326 // Parallel region 327 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 328 // Termination 329 tbb::task_scheduler_handle ctl = tbb::task_scheduler_handle::get(); 330 bool res = tbb::finalize(ctl, std::nothrow); 331 REQUIRE(res); 332 // New parallel region 333 tbb::parallel_for(0, 10, TestBlockingTerminateNS::EmptyBody()); 334 } 335 336