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