xref: /oneTBB/test/tbb/test_global_control.cpp (revision c4a799df)
151c0b2f7Stbbdev /*
2c4568449SPavel Kumbrasev     Copyright (c) 2005-2023 Intel Corporation
351c0b2f7Stbbdev 
451c0b2f7Stbbdev     Licensed under the Apache License, Version 2.0 (the "License");
551c0b2f7Stbbdev     you may not use this file except in compliance with the License.
651c0b2f7Stbbdev     You may obtain a copy of the License at
751c0b2f7Stbbdev 
851c0b2f7Stbbdev         http://www.apache.org/licenses/LICENSE-2.0
951c0b2f7Stbbdev 
1051c0b2f7Stbbdev     Unless required by applicable law or agreed to in writing, software
1151c0b2f7Stbbdev     distributed under the License is distributed on an "AS IS" BASIS,
1251c0b2f7Stbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351c0b2f7Stbbdev     See the License for the specific language governing permissions and
1451c0b2f7Stbbdev     limitations under the License.
1551c0b2f7Stbbdev */
1651c0b2f7Stbbdev 
1751c0b2f7Stbbdev //! \file test_global_control.cpp
1851c0b2f7Stbbdev //! \brief Test for [sched.global_control] specification
1951c0b2f7Stbbdev 
2051c0b2f7Stbbdev #include "common/test.h"
2151c0b2f7Stbbdev 
2251c0b2f7Stbbdev #include "common/utils.h"
2351c0b2f7Stbbdev #include "common/spin_barrier.h"
2451c0b2f7Stbbdev #include "common/utils_concurrency_limit.h"
2551c0b2f7Stbbdev 
2651c0b2f7Stbbdev #include "tbb/global_control.h"
2751c0b2f7Stbbdev #include "tbb/parallel_for.h"
2851c0b2f7Stbbdev #include "tbb/task_group.h"
2951c0b2f7Stbbdev #include "tbb/task_arena.h"
3051c0b2f7Stbbdev 
3151c0b2f7Stbbdev #include <cstring>
3251c0b2f7Stbbdev 
3351c0b2f7Stbbdev struct task_scheduler_handle_guard {
3451c0b2f7Stbbdev     tbb::task_scheduler_handle m_handle{};
3551c0b2f7Stbbdev 
task_scheduler_handle_guardtask_scheduler_handle_guard3651c0b2f7Stbbdev     task_scheduler_handle_guard() {
375fc0a5f6SAlex         m_handle = tbb::task_scheduler_handle{tbb::attach{}};
3851c0b2f7Stbbdev     }
3951c0b2f7Stbbdev 
~task_scheduler_handle_guardtask_scheduler_handle_guard4051c0b2f7Stbbdev     ~task_scheduler_handle_guard() {
415fc0a5f6SAlex         m_handle.release();
4251c0b2f7Stbbdev     }
4351c0b2f7Stbbdev 
gettask_scheduler_handle_guard4451c0b2f7Stbbdev     tbb::task_scheduler_handle& get() {
4551c0b2f7Stbbdev         return m_handle;
4651c0b2f7Stbbdev     }
4751c0b2f7Stbbdev };
4851c0b2f7Stbbdev 
4951c0b2f7Stbbdev namespace TestBlockingTerminateNS {
5051c0b2f7Stbbdev 
5151c0b2f7Stbbdev     struct TestAutoInitBody {
operator ()TestBlockingTerminateNS::TestAutoInitBody5251c0b2f7Stbbdev         void operator()( int ) const {
535fc0a5f6SAlex             tbb::parallel_for( 0, 100, utils::DummyBody() );
5451c0b2f7Stbbdev         }
5551c0b2f7Stbbdev     };
5651c0b2f7Stbbdev 
5751c0b2f7Stbbdev     static std::atomic<int> gSeed;
5851c0b2f7Stbbdev     static std::atomic<int> gNumSuccesses;
5951c0b2f7Stbbdev 
6051c0b2f7Stbbdev     class TestMultpleWaitBody {
6151c0b2f7Stbbdev         bool myAutoInit;
6251c0b2f7Stbbdev     public:
TestMultpleWaitBody(bool autoInit=false)6351c0b2f7Stbbdev         TestMultpleWaitBody( bool autoInit = false ) : myAutoInit( autoInit ) {}
operator ()(int) const6451c0b2f7Stbbdev         void operator()( int ) const {
6551c0b2f7Stbbdev             task_scheduler_handle_guard init;
6651c0b2f7Stbbdev             if ( !myAutoInit ) {
675fc0a5f6SAlex                 tbb::parallel_for(0, 10, utils::DummyBody());
6851c0b2f7Stbbdev             }
6951c0b2f7Stbbdev             utils::FastRandom<> rnd( ++gSeed );
7051c0b2f7Stbbdev             // In case of auto init sub-tests we skip
7151c0b2f7Stbbdev             //  - case #4 to avoid recursion
7251c0b2f7Stbbdev             //  - case #5 because it is explicit initialization
7351c0b2f7Stbbdev             const int numCases = myAutoInit ? 4 : 6;
7451c0b2f7Stbbdev             switch ( rnd.get() % numCases ) {
7551c0b2f7Stbbdev             case 0: {
7651c0b2f7Stbbdev                 tbb::task_arena a;
775fc0a5f6SAlex                 a.enqueue(utils::DummyBody() );
7851c0b2f7Stbbdev                 break;
7951c0b2f7Stbbdev             }
8051c0b2f7Stbbdev             case 1: {
8151c0b2f7Stbbdev                 tbb::task_group tg;
825fc0a5f6SAlex                 utils::DummyBody eb;
8351c0b2f7Stbbdev                 tg.run( eb );
8451c0b2f7Stbbdev                 tg.wait();
8551c0b2f7Stbbdev                 break;
8651c0b2f7Stbbdev             }
8751c0b2f7Stbbdev             case 2:
885fc0a5f6SAlex                 tbb::parallel_for( 0, 100, utils::DummyBody() );
8951c0b2f7Stbbdev                 break;
9051c0b2f7Stbbdev             case 3:
9151c0b2f7Stbbdev                 /* do nothing */
9251c0b2f7Stbbdev                 break;
9351c0b2f7Stbbdev             case 4:
9451c0b2f7Stbbdev                 // Create and join several threads with auto initialized scheduler.
9551c0b2f7Stbbdev                 utils::NativeParallelFor( rnd.get() % 5 + 1, TestMultpleWaitBody( true ) );
9651c0b2f7Stbbdev                 break;
9751c0b2f7Stbbdev             case 5:
9851c0b2f7Stbbdev                 {
9951c0b2f7Stbbdev                     task_scheduler_handle_guard init2;
10051c0b2f7Stbbdev                     bool res = tbb::finalize( init2.get(), std::nothrow );
10151c0b2f7Stbbdev                     REQUIRE( !res );
10251c0b2f7Stbbdev                 }
10351c0b2f7Stbbdev                 break;
10451c0b2f7Stbbdev             }
10551c0b2f7Stbbdev             if ( !myAutoInit && tbb::finalize( init.get(), std::nothrow ) )
10651c0b2f7Stbbdev                 ++gNumSuccesses;
10751c0b2f7Stbbdev         }
10851c0b2f7Stbbdev     };
10951c0b2f7Stbbdev 
TestMultpleWait()11051c0b2f7Stbbdev     void TestMultpleWait() {
11151c0b2f7Stbbdev         const int minThreads = 1;
11251c0b2f7Stbbdev         const int maxThreads = 16;
11351c0b2f7Stbbdev         const int numRepeats = 5;
11451c0b2f7Stbbdev         // Initialize seed with different values on different machines.
11551c0b2f7Stbbdev         gSeed = tbb::this_task_arena::max_concurrency();
11651c0b2f7Stbbdev         for ( int repeats = 0; repeats<numRepeats; ++repeats ) {
11751c0b2f7Stbbdev             for ( int threads = minThreads; threads<maxThreads; ++threads ) {
11851c0b2f7Stbbdev                 gNumSuccesses = 0;
11951c0b2f7Stbbdev                 utils::NativeParallelFor( threads, TestMultpleWaitBody() );
12051c0b2f7Stbbdev                 REQUIRE_MESSAGE( gNumSuccesses > 0, "At least one blocking terminate must return 'true'" );
12151c0b2f7Stbbdev             }
12251c0b2f7Stbbdev         }
12351c0b2f7Stbbdev     }
12451c0b2f7Stbbdev 
12551c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
12651c0b2f7Stbbdev     template <typename F>
TestException(F & f)12751c0b2f7Stbbdev     void TestException( F &f ) {
12851c0b2f7Stbbdev         utils::suppress_unused_warning( f );
12951c0b2f7Stbbdev         bool caught = false;
13051c0b2f7Stbbdev         try {
13151c0b2f7Stbbdev             f();
13251c0b2f7Stbbdev             REQUIRE( false );
13351c0b2f7Stbbdev         }
13451c0b2f7Stbbdev         catch ( const tbb::unsafe_wait& e) {
13551c0b2f7Stbbdev             const char* msg = e.what();
13651c0b2f7Stbbdev             REQUIRE((msg && std::strlen(msg) != 0));
13751c0b2f7Stbbdev             caught = true;
13851c0b2f7Stbbdev         }
13951c0b2f7Stbbdev         catch ( ... ) {
14051c0b2f7Stbbdev             REQUIRE( false );
14151c0b2f7Stbbdev         }
14251c0b2f7Stbbdev         REQUIRE( caught );
14351c0b2f7Stbbdev     }
14451c0b2f7Stbbdev 
14551c0b2f7Stbbdev     class ExceptionTest1 {
14651c0b2f7Stbbdev         task_scheduler_handle_guard tsi1;
14751c0b2f7Stbbdev         int myIndex;
14851c0b2f7Stbbdev 
14951c0b2f7Stbbdev     public:
ExceptionTest1(int index)15051c0b2f7Stbbdev         ExceptionTest1( int index ) : myIndex( index ) {}
15151c0b2f7Stbbdev 
operator ()()15251c0b2f7Stbbdev         void operator()() {
15351c0b2f7Stbbdev             task_scheduler_handle_guard tsi2;
1545fc0a5f6SAlex             tbb::parallel_for(0, 2, utils::DummyBody()); // auto-init
15551c0b2f7Stbbdev             tbb::finalize((myIndex == 0 ? tsi1.get() : tsi2.get()));
15651c0b2f7Stbbdev             REQUIRE_MESSAGE( false, "Blocking terminate did not throw the exception" );
15751c0b2f7Stbbdev         }
15851c0b2f7Stbbdev     };
15951c0b2f7Stbbdev 
16051c0b2f7Stbbdev     struct ExceptionTest2 {
16151c0b2f7Stbbdev         class Body {
16251c0b2f7Stbbdev             utils::SpinBarrier& myBarrier;
16351c0b2f7Stbbdev         public:
Body(utils::SpinBarrier & barrier)16451c0b2f7Stbbdev             Body( utils::SpinBarrier& barrier ) : myBarrier( barrier ) {}
operator ()(int) const16551c0b2f7Stbbdev             void operator()( int ) const {
16651c0b2f7Stbbdev                 myBarrier.wait();
16751c0b2f7Stbbdev                 task_scheduler_handle_guard init;
16851c0b2f7Stbbdev                 tbb::finalize( init.get() );
16951c0b2f7Stbbdev                 REQUIRE_MESSAGE( false, "Blocking terminate did not throw the exception inside the parallel region" );
17051c0b2f7Stbbdev             }
17151c0b2f7Stbbdev         };
operator ()TestBlockingTerminateNS::ExceptionTest217251c0b2f7Stbbdev         void operator()() {
17351c0b2f7Stbbdev             const int numThreads = 4;
17451c0b2f7Stbbdev             tbb::global_control init(tbb::global_control::max_allowed_parallelism, numThreads);
175478de5b1Stbbdev             tbb::task_arena a(numThreads);
176478de5b1Stbbdev             a.execute([&] {
17751c0b2f7Stbbdev                 utils::SpinBarrier barrier(numThreads);
17851c0b2f7Stbbdev                 tbb::parallel_for(0, numThreads, Body(barrier));
17951c0b2f7Stbbdev                 REQUIRE_MESSAGE(false, "Parallel loop did not throw the exception");
180478de5b1Stbbdev             });
18151c0b2f7Stbbdev         }
18251c0b2f7Stbbdev     };
18351c0b2f7Stbbdev 
TestExceptions()18451c0b2f7Stbbdev     void TestExceptions() {
1858b6f831cStbbdev         ExceptionTest1 Test1(0);
18651c0b2f7Stbbdev         TestException( Test1 );
1878b6f831cStbbdev         ExceptionTest1 Test2(1);
18851c0b2f7Stbbdev         TestException( Test2 );
1898b6f831cStbbdev         if (utils::get_platform_max_threads() > 1) {
1908b6f831cStbbdev             // TODO: Fix the arena leak issue on single threaded machine
1918b6f831cStbbdev             // (see https://github.com/oneapi-src/oneTBB/issues/396)
19251c0b2f7Stbbdev             ExceptionTest2 Test3;
19351c0b2f7Stbbdev             TestException(Test3);
19451c0b2f7Stbbdev         }
1958b6f831cStbbdev     }
19651c0b2f7Stbbdev 
19751c0b2f7Stbbdev #endif /* TBB_USE_EXCEPTIONS */
19851c0b2f7Stbbdev 
19951c0b2f7Stbbdev } // namespace TestBlockingTerminateNS
20051c0b2f7Stbbdev 
TestTerminationAndAutoinit(bool autoinit)20151c0b2f7Stbbdev void TestTerminationAndAutoinit(bool autoinit) {
20251c0b2f7Stbbdev     task_scheduler_handle_guard ctl1;
20351c0b2f7Stbbdev     task_scheduler_handle_guard ctl2;
20451c0b2f7Stbbdev 
20551c0b2f7Stbbdev     if (autoinit) {
2065fc0a5f6SAlex         tbb::parallel_for(0, 10, utils::DummyBody());
20751c0b2f7Stbbdev     }
20851c0b2f7Stbbdev     bool res1 = tbb::finalize(ctl1.get(), std::nothrow);
20951c0b2f7Stbbdev     if (autoinit) {
21051c0b2f7Stbbdev         REQUIRE(!res1);
21151c0b2f7Stbbdev     } else {
21251c0b2f7Stbbdev         REQUIRE(res1);
21351c0b2f7Stbbdev     }
21451c0b2f7Stbbdev     bool res2 = tbb::finalize(ctl2.get(), std::nothrow);
21551c0b2f7Stbbdev     REQUIRE(res2);
21651c0b2f7Stbbdev }
21751c0b2f7Stbbdev 
2189924f9e8SIlya Isaev //! Check no reference leak for an external thread
2199924f9e8SIlya Isaev //! \brief \ref regression \ref error_guessing
2209924f9e8SIlya Isaev TEST_CASE("test decrease reference") {
2215fc0a5f6SAlex     tbb::task_scheduler_handle handle = tbb::task_scheduler_handle{tbb::attach{}};
2229924f9e8SIlya Isaev 
__anon8f1bc23b0302(int) 2239924f9e8SIlya Isaev     std::thread thr([] { tbb::parallel_for(0, 1, [](int) {}); } );
2249924f9e8SIlya Isaev     thr.join();
2259924f9e8SIlya Isaev 
2269924f9e8SIlya Isaev     REQUIRE(tbb::finalize(handle, std::nothrow));
2279924f9e8SIlya Isaev }
2289924f9e8SIlya Isaev 
2295fc0a5f6SAlex //! Testing lifetime control
2305fc0a5f6SAlex //! \brief \ref error_guessing
2315fc0a5f6SAlex TEST_CASE("prolong lifetime auto init") {
23251c0b2f7Stbbdev     TestTerminationAndAutoinit(false);
23351c0b2f7Stbbdev     TestTerminationAndAutoinit(true);
23451c0b2f7Stbbdev }
23551c0b2f7Stbbdev 
23651c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
23751c0b2f7Stbbdev //! Testing lifetime control advanced
2385fc0a5f6SAlex //! \brief \ref error_guessing
23951c0b2f7Stbbdev TEST_CASE("prolong lifetime advanced") {
240*c4a799dfSJhaShweta1     // Exceptions test leaves auto-initialized scheduler after,
24151c0b2f7Stbbdev     // because all blocking terminate calls are inside the parallel region,
24251c0b2f7Stbbdev     // thus resulting in false termination result.
24351c0b2f7Stbbdev     utils::NativeParallelFor(1,
__anon8f1bc23b0402(int) 24451c0b2f7Stbbdev         [&](int) { TestBlockingTerminateNS::TestExceptions(); });
24551c0b2f7Stbbdev }
24651c0b2f7Stbbdev #endif
24751c0b2f7Stbbdev 
24851c0b2f7Stbbdev //! Testing multiple wait
2495fc0a5f6SAlex //! \brief \ref error_guessing
25051c0b2f7Stbbdev TEST_CASE("prolong lifetime multiple wait") {
25151c0b2f7Stbbdev     TestBlockingTerminateNS::TestMultpleWait();
25251c0b2f7Stbbdev }
25351c0b2f7Stbbdev 
254c4568449SPavel Kumbrasev //! \brief \ref regression
255c4568449SPavel Kumbrasev TEST_CASE("test concurrent task_scheduler_handle destruction") {
256c4568449SPavel Kumbrasev     std::atomic<bool> stop{ false };
__anon8f1bc23b0502null257c4568449SPavel Kumbrasev     std::thread thr1([&] {
258c4568449SPavel Kumbrasev         while (!stop) {
259c4568449SPavel Kumbrasev             auto h = tbb::task_scheduler_handle{ tbb::attach{} };
260c4568449SPavel Kumbrasev             tbb::finalize(h, std::nothrow_t{});
261c4568449SPavel Kumbrasev         }
262c4568449SPavel Kumbrasev     });
263c4568449SPavel Kumbrasev 
264c4568449SPavel Kumbrasev     for (int i = 0; i < 1000; ++i) {
__anon8f1bc23b0602null265c4568449SPavel Kumbrasev         std::thread thr2([] {
266c4568449SPavel Kumbrasev             tbb::parallel_for(0, 1, [](int) {});
267c4568449SPavel Kumbrasev         });
268c4568449SPavel Kumbrasev         thr2.join();
269c4568449SPavel Kumbrasev     }
270c4568449SPavel Kumbrasev     stop = true;
271c4568449SPavel Kumbrasev     thr1.join();
272c4568449SPavel Kumbrasev }
273