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