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 #include "common/test.h" 21 22 #include "common/utils.h" 23 #include "common/spin_barrier.h" 24 #include "common/utils_concurrency_limit.h" 25 26 #include "tbb/global_control.h" 27 #include "tbb/parallel_for.h" 28 #include "tbb/task_group.h" 29 #include "tbb/task_arena.h" 30 31 #include <cstring> 32 33 struct task_scheduler_handle_guard { 34 tbb::task_scheduler_handle m_handle{}; 35 36 task_scheduler_handle_guard() { 37 m_handle = tbb::task_scheduler_handle{tbb::attach{}}; 38 } 39 40 ~task_scheduler_handle_guard() { 41 m_handle.release(); 42 } 43 44 tbb::task_scheduler_handle& get() { 45 return m_handle; 46 } 47 }; 48 49 namespace TestBlockingTerminateNS { 50 51 struct TestAutoInitBody { 52 void operator()( int ) const { 53 tbb::parallel_for( 0, 100, utils::DummyBody() ); 54 } 55 }; 56 57 static std::atomic<int> gSeed; 58 static std::atomic<int> gNumSuccesses; 59 60 class TestMultpleWaitBody { 61 bool myAutoInit; 62 public: 63 TestMultpleWaitBody( bool autoInit = false ) : myAutoInit( autoInit ) {} 64 void operator()( int ) const { 65 task_scheduler_handle_guard init; 66 if ( !myAutoInit ) { 67 tbb::parallel_for(0, 10, utils::DummyBody()); 68 } 69 utils::FastRandom<> rnd( ++gSeed ); 70 // In case of auto init sub-tests we skip 71 // - case #4 to avoid recursion 72 // - case #5 because it is explicit initialization 73 const int numCases = myAutoInit ? 4 : 6; 74 switch ( rnd.get() % numCases ) { 75 case 0: { 76 tbb::task_arena a; 77 a.enqueue(utils::DummyBody() ); 78 break; 79 } 80 case 1: { 81 tbb::task_group tg; 82 utils::DummyBody eb; 83 tg.run( eb ); 84 tg.wait(); 85 break; 86 } 87 case 2: 88 tbb::parallel_for( 0, 100, utils::DummyBody() ); 89 break; 90 case 3: 91 /* do nothing */ 92 break; 93 case 4: 94 // Create and join several threads with auto initialized scheduler. 95 utils::NativeParallelFor( rnd.get() % 5 + 1, TestMultpleWaitBody( true ) ); 96 break; 97 case 5: 98 { 99 task_scheduler_handle_guard init2; 100 bool res = tbb::finalize( init2.get(), std::nothrow ); 101 REQUIRE( !res ); 102 } 103 break; 104 } 105 if ( !myAutoInit && tbb::finalize( init.get(), std::nothrow ) ) 106 ++gNumSuccesses; 107 } 108 }; 109 110 void TestMultpleWait() { 111 const int minThreads = 1; 112 const int maxThreads = 16; 113 const int numRepeats = 5; 114 // Initialize seed with different values on different machines. 115 gSeed = tbb::this_task_arena::max_concurrency(); 116 for ( int repeats = 0; repeats<numRepeats; ++repeats ) { 117 for ( int threads = minThreads; threads<maxThreads; ++threads ) { 118 gNumSuccesses = 0; 119 utils::NativeParallelFor( threads, TestMultpleWaitBody() ); 120 REQUIRE_MESSAGE( gNumSuccesses > 0, "At least one blocking terminate must return 'true'" ); 121 } 122 } 123 } 124 125 #if TBB_USE_EXCEPTIONS 126 template <typename F> 127 void TestException( F &f ) { 128 utils::suppress_unused_warning( f ); 129 bool caught = false; 130 try { 131 f(); 132 REQUIRE( false ); 133 } 134 catch ( const tbb::unsafe_wait& e) { 135 const char* msg = e.what(); 136 REQUIRE((msg && std::strlen(msg) != 0)); 137 caught = true; 138 } 139 catch ( ... ) { 140 REQUIRE( false ); 141 } 142 REQUIRE( caught ); 143 } 144 145 class ExceptionTest1 { 146 task_scheduler_handle_guard tsi1; 147 int myIndex; 148 149 public: 150 ExceptionTest1( int index ) : myIndex( index ) {} 151 152 void operator()() { 153 task_scheduler_handle_guard tsi2; 154 tbb::parallel_for(0, 2, utils::DummyBody()); // auto-init 155 tbb::finalize((myIndex == 0 ? tsi1.get() : tsi2.get())); 156 REQUIRE_MESSAGE( false, "Blocking terminate did not throw the exception" ); 157 } 158 }; 159 160 struct ExceptionTest2 { 161 class Body { 162 utils::SpinBarrier& myBarrier; 163 public: 164 Body( utils::SpinBarrier& barrier ) : myBarrier( barrier ) {} 165 void operator()( int ) const { 166 myBarrier.wait(); 167 task_scheduler_handle_guard init; 168 tbb::finalize( init.get() ); 169 REQUIRE_MESSAGE( false, "Blocking terminate did not throw the exception inside the parallel region" ); 170 } 171 }; 172 void operator()() { 173 const int numThreads = 4; 174 tbb::global_control init(tbb::global_control::max_allowed_parallelism, numThreads); 175 tbb::task_arena a(numThreads); 176 a.execute([&] { 177 utils::SpinBarrier barrier(numThreads); 178 tbb::parallel_for(0, numThreads, Body(barrier)); 179 REQUIRE_MESSAGE(false, "Parallel loop did not throw the exception"); 180 }); 181 } 182 }; 183 184 void TestExceptions() { 185 ExceptionTest1 Test1(0); 186 TestException( Test1 ); 187 ExceptionTest1 Test2(1); 188 TestException( Test2 ); 189 if (utils::get_platform_max_threads() > 1) { 190 // TODO: Fix the arena leak issue on single threaded machine 191 // (see https://github.com/oneapi-src/oneTBB/issues/396) 192 ExceptionTest2 Test3; 193 TestException(Test3); 194 } 195 } 196 197 #endif /* TBB_USE_EXCEPTIONS */ 198 199 } // namespace TestBlockingTerminateNS 200 201 void TestTerminationAndAutoinit(bool autoinit) { 202 task_scheduler_handle_guard ctl1; 203 task_scheduler_handle_guard ctl2; 204 205 if (autoinit) { 206 tbb::parallel_for(0, 10, utils::DummyBody()); 207 } 208 bool res1 = tbb::finalize(ctl1.get(), std::nothrow); 209 if (autoinit) { 210 REQUIRE(!res1); 211 } else { 212 REQUIRE(res1); 213 } 214 bool res2 = tbb::finalize(ctl2.get(), std::nothrow); 215 REQUIRE(res2); 216 } 217 218 //! Check no reference leak for an external thread 219 //! \brief \ref regression \ref error_guessing 220 TEST_CASE("test decrease reference") { 221 tbb::task_scheduler_handle handle = tbb::task_scheduler_handle{tbb::attach{}}; 222 223 std::thread thr([] { tbb::parallel_for(0, 1, [](int) {}); } ); 224 thr.join(); 225 226 REQUIRE(tbb::finalize(handle, std::nothrow)); 227 } 228 229 //! Testing lifetime control 230 //! \brief \ref error_guessing 231 TEST_CASE("prolong lifetime auto init") { 232 TestTerminationAndAutoinit(false); 233 TestTerminationAndAutoinit(true); 234 } 235 236 #if TBB_USE_EXCEPTIONS 237 //! Testing lifetime control advanced 238 //! \brief \ref error_guessing 239 TEST_CASE("prolong lifetime advanced") { 240 // Exceptions test leaves auto-initialized sheduler after, 241 // because all blocking terminate calls are inside the parallel region, 242 // thus resulting in false termination result. 243 utils::NativeParallelFor(1, 244 [&](int) { TestBlockingTerminateNS::TestExceptions(); }); 245 } 246 #endif 247 248 //! Testing multiple wait 249 //! \brief \ref error_guessing 250 TEST_CASE("prolong lifetime multiple wait") { 251 TestBlockingTerminateNS::TestMultpleWait(); 252 } 253 254