1 /*
2 Copyright (c) 2005-2023 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
task_scheduler_handle_guardtask_scheduler_handle_guard36 task_scheduler_handle_guard() {
37 m_handle = tbb::task_scheduler_handle{tbb::attach{}};
38 }
39
~task_scheduler_handle_guardtask_scheduler_handle_guard40 ~task_scheduler_handle_guard() {
41 m_handle.release();
42 }
43
gettask_scheduler_handle_guard44 tbb::task_scheduler_handle& get() {
45 return m_handle;
46 }
47 };
48
49 namespace TestBlockingTerminateNS {
50
51 struct TestAutoInitBody {
operator ()TestBlockingTerminateNS::TestAutoInitBody52 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:
TestMultpleWaitBody(bool autoInit=false)63 TestMultpleWaitBody( bool autoInit = false ) : myAutoInit( autoInit ) {}
operator ()(int) const64 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
TestMultpleWait()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>
TestException(F & 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:
ExceptionTest1(int index)150 ExceptionTest1( int index ) : myIndex( index ) {}
151
operator ()()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:
Body(utils::SpinBarrier & barrier)164 Body( utils::SpinBarrier& barrier ) : myBarrier( barrier ) {}
operator ()(int) const165 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 };
operator ()TestBlockingTerminateNS::ExceptionTest2172 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
TestExceptions()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
TestTerminationAndAutoinit(bool autoinit)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
__anon8f1bc23b0302(int) 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 scheduler after,
241 // because all blocking terminate calls are inside the parallel region,
242 // thus resulting in false termination result.
243 utils::NativeParallelFor(1,
__anon8f1bc23b0402(int) 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 //! \brief \ref regression
255 TEST_CASE("test concurrent task_scheduler_handle destruction") {
256 std::atomic<bool> stop{ false };
__anon8f1bc23b0502null257 std::thread thr1([&] {
258 while (!stop) {
259 auto h = tbb::task_scheduler_handle{ tbb::attach{} };
260 tbb::finalize(h, std::nothrow_t{});
261 }
262 });
263
264 for (int i = 0; i < 1000; ++i) {
__anon8f1bc23b0602null265 std::thread thr2([] {
266 tbb::parallel_for(0, 1, [](int) {});
267 });
268 thr2.join();
269 }
270 stop = true;
271 thr1.join();
272 }
273