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