xref: /oneTBB/test/tbb/test_global_control.cpp (revision 6caecf96)
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