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