xref: /oneTBB/test/tbb/test_task_group.cpp (revision 7cee2251)
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 #include "common/test.h"
1851c0b2f7Stbbdev #include "common/utils.h"
1949e08aacStbbdev #include "oneapi/tbb/detail/_config.h"
2051c0b2f7Stbbdev #include "tbb/global_control.h"
2151c0b2f7Stbbdev 
2251c0b2f7Stbbdev #include "tbb/task_group.h"
2351c0b2f7Stbbdev 
2451c0b2f7Stbbdev #include "common/concurrency_tracker.h"
2551c0b2f7Stbbdev 
2651c0b2f7Stbbdev #include <atomic>
27478de5b1Stbbdev #include <stdexcept>
2851c0b2f7Stbbdev 
2951c0b2f7Stbbdev //! \file test_task_group.cpp
3051c0b2f7Stbbdev //! \brief Test for [scheduler.task_group scheduler.task_group_status] specification
3151c0b2f7Stbbdev 
3251c0b2f7Stbbdev unsigned g_MaxConcurrency = 4;
3351c0b2f7Stbbdev using atomic_t = std::atomic<std::uintptr_t>;
3451c0b2f7Stbbdev unsigned MinThread = 1;
3551c0b2f7Stbbdev unsigned MaxThread = 4;
3651c0b2f7Stbbdev 
3751c0b2f7Stbbdev //------------------------------------------------------------------------
3851c0b2f7Stbbdev // Tests for the thread safety of the task_group manipulations
3951c0b2f7Stbbdev //------------------------------------------------------------------------
4051c0b2f7Stbbdev 
4151c0b2f7Stbbdev #include "common/spin_barrier.h"
4251c0b2f7Stbbdev 
4351c0b2f7Stbbdev enum SharingMode {
4451c0b2f7Stbbdev     VagabondGroup = 1,
4551c0b2f7Stbbdev     ParallelWait = 2
4651c0b2f7Stbbdev };
4751c0b2f7Stbbdev 
4851c0b2f7Stbbdev template<typename task_group_type>
4951c0b2f7Stbbdev class SharedGroupBodyImpl : utils::NoCopy, utils::NoAfterlife {
5051c0b2f7Stbbdev     static const std::uintptr_t c_numTasks0 = 4096,
5151c0b2f7Stbbdev                         c_numTasks1 = 1024;
5251c0b2f7Stbbdev 
5351c0b2f7Stbbdev     const std::uintptr_t m_numThreads;
5451c0b2f7Stbbdev     const std::uintptr_t m_sharingMode;
5551c0b2f7Stbbdev 
5651c0b2f7Stbbdev     task_group_type *m_taskGroup;
5751c0b2f7Stbbdev     atomic_t m_tasksSpawned,
5851c0b2f7Stbbdev              m_threadsReady;
5951c0b2f7Stbbdev     utils::SpinBarrier m_barrier;
6051c0b2f7Stbbdev 
6151c0b2f7Stbbdev     static atomic_t s_tasksExecuted;
6251c0b2f7Stbbdev 
6351c0b2f7Stbbdev     struct TaskFunctor {
6451c0b2f7Stbbdev         SharedGroupBodyImpl *m_pOwner;
operator ()SharedGroupBodyImpl::TaskFunctor6551c0b2f7Stbbdev         void operator () () const {
6651c0b2f7Stbbdev             if ( m_pOwner->m_sharingMode & ParallelWait ) {
6751c0b2f7Stbbdev                 while ( utils::ConcurrencyTracker::PeakParallelism() < m_pOwner->m_numThreads )
68b15aabb3Stbbdev                     utils::yield();
6951c0b2f7Stbbdev             }
7051c0b2f7Stbbdev             ++s_tasksExecuted;
7151c0b2f7Stbbdev         }
7251c0b2f7Stbbdev     };
7351c0b2f7Stbbdev 
7451c0b2f7Stbbdev     TaskFunctor m_taskFunctor;
7551c0b2f7Stbbdev 
Spawn(std::uintptr_t numTasks)7651c0b2f7Stbbdev     void Spawn ( std::uintptr_t numTasks ) {
7751c0b2f7Stbbdev         for ( std::uintptr_t i = 0; i < numTasks; ++i ) {
7851c0b2f7Stbbdev             ++m_tasksSpawned;
7951c0b2f7Stbbdev             utils::ConcurrencyTracker ct;
8051c0b2f7Stbbdev             m_taskGroup->run( m_taskFunctor );
8151c0b2f7Stbbdev         }
8251c0b2f7Stbbdev         ++m_threadsReady;
8351c0b2f7Stbbdev     }
8451c0b2f7Stbbdev 
DeleteTaskGroup()8551c0b2f7Stbbdev     void DeleteTaskGroup () {
8651c0b2f7Stbbdev         delete m_taskGroup;
8757f524caSIlya Isaev         m_taskGroup = nullptr;
8851c0b2f7Stbbdev     }
8951c0b2f7Stbbdev 
Wait()9051c0b2f7Stbbdev     void Wait () {
9151c0b2f7Stbbdev         while ( m_threadsReady != m_numThreads )
92b15aabb3Stbbdev             utils::yield();
9351c0b2f7Stbbdev         const std::uintptr_t numSpawned = c_numTasks0 + c_numTasks1 * (m_numThreads - 1);
9451c0b2f7Stbbdev         CHECK_MESSAGE( m_tasksSpawned == numSpawned, "Wrong number of spawned tasks. The test is broken" );
9551c0b2f7Stbbdev         INFO("Max spawning parallelism is " << utils::ConcurrencyTracker::PeakParallelism() << "out of " << g_MaxConcurrency);
9651c0b2f7Stbbdev         if ( m_sharingMode & ParallelWait ) {
9751c0b2f7Stbbdev             m_barrier.wait( &utils::ConcurrencyTracker::Reset );
9851c0b2f7Stbbdev             {
9951c0b2f7Stbbdev                 utils::ConcurrencyTracker ct;
10051c0b2f7Stbbdev                 m_taskGroup->wait();
10151c0b2f7Stbbdev             }
10237a89d47SIlya Isaev             if ( utils::ConcurrencyTracker::PeakParallelism() == 1 ) {
10337a89d47SIlya Isaev                 const char* msg = "Warning: No parallel waiting detected in TestParallelWait";
10437a89d47SIlya Isaev                 WARN( msg );
10537a89d47SIlya Isaev             }
10651c0b2f7Stbbdev             m_barrier.wait();
10751c0b2f7Stbbdev         }
10851c0b2f7Stbbdev         else
10951c0b2f7Stbbdev             m_taskGroup->wait();
11051c0b2f7Stbbdev         CHECK_MESSAGE( m_tasksSpawned == numSpawned, "No tasks should be spawned after wait starts. The test is broken" );
11151c0b2f7Stbbdev         CHECK_MESSAGE( s_tasksExecuted == numSpawned, "Not all spawned tasks were executed" );
11251c0b2f7Stbbdev     }
11351c0b2f7Stbbdev 
11451c0b2f7Stbbdev public:
SharedGroupBodyImpl(std::uintptr_t numThreads,std::uintptr_t sharingMode=0)11551c0b2f7Stbbdev     SharedGroupBodyImpl ( std::uintptr_t numThreads, std::uintptr_t sharingMode = 0 )
11651c0b2f7Stbbdev         : m_numThreads(numThreads)
11751c0b2f7Stbbdev         , m_sharingMode(sharingMode)
11857f524caSIlya Isaev         , m_taskGroup(nullptr)
11951c0b2f7Stbbdev         , m_barrier(numThreads)
12051c0b2f7Stbbdev     {
12151c0b2f7Stbbdev         CHECK_MESSAGE( m_numThreads > 1, "SharedGroupBody tests require concurrency" );
12251c0b2f7Stbbdev         if ((m_sharingMode & VagabondGroup) && m_numThreads != 2) {
12351c0b2f7Stbbdev             CHECK_MESSAGE(false, "In vagabond mode SharedGroupBody must be used with 2 threads only");
12451c0b2f7Stbbdev         }
12551c0b2f7Stbbdev         utils::ConcurrencyTracker::Reset();
12651c0b2f7Stbbdev         s_tasksExecuted = 0;
12751c0b2f7Stbbdev         m_tasksSpawned = 0;
12851c0b2f7Stbbdev         m_threadsReady = 0;
12951c0b2f7Stbbdev         m_taskFunctor.m_pOwner = this;
13051c0b2f7Stbbdev     }
13151c0b2f7Stbbdev 
Run(std::uintptr_t idx)13251c0b2f7Stbbdev     void Run ( std::uintptr_t idx ) {
13351c0b2f7Stbbdev         AssertLive();
13451c0b2f7Stbbdev         if ( idx == 0 ) {
13551c0b2f7Stbbdev             if (m_taskGroup || m_tasksSpawned) {
13651c0b2f7Stbbdev                 CHECK_MESSAGE(false, "SharedGroupBody must be reset before reuse");
13751c0b2f7Stbbdev             }
13851c0b2f7Stbbdev             m_taskGroup = new task_group_type;
13951c0b2f7Stbbdev             Spawn( c_numTasks0 );
14051c0b2f7Stbbdev             Wait();
14151c0b2f7Stbbdev             if ( m_sharingMode & VagabondGroup )
14251c0b2f7Stbbdev                 m_barrier.wait();
14351c0b2f7Stbbdev             else
14451c0b2f7Stbbdev                 DeleteTaskGroup();
14551c0b2f7Stbbdev         }
14651c0b2f7Stbbdev         else {
14751c0b2f7Stbbdev             while ( m_tasksSpawned == 0 )
148b15aabb3Stbbdev                 utils::yield();
14951c0b2f7Stbbdev             CHECK_MESSAGE ( m_taskGroup, "Task group is not initialized");
15051c0b2f7Stbbdev             Spawn (c_numTasks1);
15151c0b2f7Stbbdev             if ( m_sharingMode & ParallelWait )
15251c0b2f7Stbbdev                 Wait();
15351c0b2f7Stbbdev             if ( m_sharingMode & VagabondGroup ) {
15451c0b2f7Stbbdev                 CHECK_MESSAGE ( idx == 1, "In vagabond mode SharedGroupBody must be used with 2 threads only" );
15551c0b2f7Stbbdev                 m_barrier.wait();
15651c0b2f7Stbbdev                 DeleteTaskGroup();
15751c0b2f7Stbbdev             }
15851c0b2f7Stbbdev         }
15951c0b2f7Stbbdev         AssertLive();
16051c0b2f7Stbbdev     }
16151c0b2f7Stbbdev };
16251c0b2f7Stbbdev 
16351c0b2f7Stbbdev template<typename task_group_type>
16451c0b2f7Stbbdev atomic_t SharedGroupBodyImpl<task_group_type>::s_tasksExecuted;
16551c0b2f7Stbbdev 
16651c0b2f7Stbbdev template<typename task_group_type>
16751c0b2f7Stbbdev class  SharedGroupBody : utils::NoAssign, utils::NoAfterlife {
16851c0b2f7Stbbdev     bool m_bOwner;
16951c0b2f7Stbbdev     SharedGroupBodyImpl<task_group_type> *m_pImpl;
17051c0b2f7Stbbdev public:
SharedGroupBody(std::uintptr_t numThreads,std::uintptr_t sharingMode=0)17151c0b2f7Stbbdev     SharedGroupBody ( std::uintptr_t numThreads, std::uintptr_t sharingMode = 0 )
17251c0b2f7Stbbdev         : utils::NoAssign()
17351c0b2f7Stbbdev         , utils::NoAfterlife()
17451c0b2f7Stbbdev         , m_bOwner(true)
17551c0b2f7Stbbdev         , m_pImpl( new SharedGroupBodyImpl<task_group_type>(numThreads, sharingMode) )
17651c0b2f7Stbbdev     {}
SharedGroupBody(const SharedGroupBody & src)17751c0b2f7Stbbdev     SharedGroupBody ( const SharedGroupBody& src )
17851c0b2f7Stbbdev         : utils::NoAssign()
17951c0b2f7Stbbdev         , utils::NoAfterlife()
18051c0b2f7Stbbdev         , m_bOwner(false)
18151c0b2f7Stbbdev         , m_pImpl(src.m_pImpl)
18251c0b2f7Stbbdev     {}
~SharedGroupBody()18351c0b2f7Stbbdev     ~SharedGroupBody () {
18451c0b2f7Stbbdev         if ( m_bOwner )
18551c0b2f7Stbbdev             delete m_pImpl;
18651c0b2f7Stbbdev     }
operator ()(std::uintptr_t idx) const18751c0b2f7Stbbdev     void operator() ( std::uintptr_t idx ) const {
18851c0b2f7Stbbdev         // Wrap the functior into additional task group to enforce bounding.
18951c0b2f7Stbbdev         task_group_type tg;
19051c0b2f7Stbbdev         tg.run_and_wait([&] { m_pImpl->Run(idx); });
19151c0b2f7Stbbdev     }
19251c0b2f7Stbbdev };
19351c0b2f7Stbbdev 
19451c0b2f7Stbbdev template<typename task_group_type>
19551c0b2f7Stbbdev class RunAndWaitSyncronizationTestBody : utils::NoAssign {
19651c0b2f7Stbbdev     utils::SpinBarrier& m_barrier;
19751c0b2f7Stbbdev     std::atomic<bool>& m_completed;
19851c0b2f7Stbbdev     task_group_type& m_tg;
19951c0b2f7Stbbdev public:
RunAndWaitSyncronizationTestBody(utils::SpinBarrier & barrier,std::atomic<bool> & completed,task_group_type & tg)20051c0b2f7Stbbdev     RunAndWaitSyncronizationTestBody(utils::SpinBarrier& barrier, std::atomic<bool>& completed, task_group_type& tg)
20151c0b2f7Stbbdev         : m_barrier(barrier), m_completed(completed), m_tg(tg) {}
20251c0b2f7Stbbdev 
operator ()() const20351c0b2f7Stbbdev     void operator()() const {
20451c0b2f7Stbbdev         m_barrier.wait();
205b15aabb3Stbbdev         utils::doDummyWork(100000);
20651c0b2f7Stbbdev         m_completed = true;
20751c0b2f7Stbbdev     }
20851c0b2f7Stbbdev 
operator ()(int id) const20951c0b2f7Stbbdev     void operator()(int id) const {
21051c0b2f7Stbbdev         if (id == 0) {
21151c0b2f7Stbbdev             m_tg.run_and_wait(*this);
21251c0b2f7Stbbdev         } else {
21351c0b2f7Stbbdev             m_barrier.wait();
21451c0b2f7Stbbdev             m_tg.wait();
21551c0b2f7Stbbdev             CHECK_MESSAGE(m_completed, "A concurrent waiter has left the wait method earlier than work has finished");
21651c0b2f7Stbbdev         }
21751c0b2f7Stbbdev     }
21851c0b2f7Stbbdev };
21951c0b2f7Stbbdev 
22051c0b2f7Stbbdev template<typename task_group_type>
TestParallelSpawn()22151c0b2f7Stbbdev void TestParallelSpawn () {
22251c0b2f7Stbbdev     NativeParallelFor( g_MaxConcurrency, SharedGroupBody<task_group_type>(g_MaxConcurrency) );
22351c0b2f7Stbbdev }
22451c0b2f7Stbbdev 
22551c0b2f7Stbbdev template<typename task_group_type>
TestParallelWait()22651c0b2f7Stbbdev void TestParallelWait () {
22751c0b2f7Stbbdev     NativeParallelFor( g_MaxConcurrency, SharedGroupBody<task_group_type>(g_MaxConcurrency, ParallelWait) );
22851c0b2f7Stbbdev 
22951c0b2f7Stbbdev     utils::SpinBarrier barrier(g_MaxConcurrency);
23051c0b2f7Stbbdev     std::atomic<bool> completed;
23151c0b2f7Stbbdev     completed = false;
23251c0b2f7Stbbdev     task_group_type tg;
23351c0b2f7Stbbdev     RunAndWaitSyncronizationTestBody<task_group_type> b(barrier, completed, tg);
23451c0b2f7Stbbdev     NativeParallelFor( g_MaxConcurrency, b );
23551c0b2f7Stbbdev }
23651c0b2f7Stbbdev 
23751c0b2f7Stbbdev // Tests non-stack-bound task group (the group that is allocated by one thread and destroyed by the other)
23851c0b2f7Stbbdev template<typename task_group_type>
TestVagabondGroup()23951c0b2f7Stbbdev void TestVagabondGroup () {
24051c0b2f7Stbbdev     NativeParallelFor( 2, SharedGroupBody<task_group_type>(2, VagabondGroup) );
24151c0b2f7Stbbdev }
24251c0b2f7Stbbdev 
24351c0b2f7Stbbdev #include "common/memory_usage.h"
24451c0b2f7Stbbdev 
24551c0b2f7Stbbdev template<typename task_group_type>
TestThreadSafety()24651c0b2f7Stbbdev void TestThreadSafety() {
24751c0b2f7Stbbdev     auto tests = [] {
24851c0b2f7Stbbdev         for (int trail = 0; trail < 10; ++trail) {
24951c0b2f7Stbbdev             TestParallelSpawn<task_group_type>();
25051c0b2f7Stbbdev             TestParallelWait<task_group_type>();
25151c0b2f7Stbbdev             TestVagabondGroup<task_group_type>();
25251c0b2f7Stbbdev         }
25351c0b2f7Stbbdev     };
25451c0b2f7Stbbdev 
25551c0b2f7Stbbdev     // Test and warm up allocator.
25651c0b2f7Stbbdev     tests();
25751c0b2f7Stbbdev 
25851c0b2f7Stbbdev     // Ensure that cosumption is stabilized.
25951c0b2f7Stbbdev     std::size_t initial = utils::GetMemoryUsage();
26049e08aacStbbdev     for (;;) {
26151c0b2f7Stbbdev         tests();
26251c0b2f7Stbbdev         std::size_t current = utils::GetMemoryUsage();
26351c0b2f7Stbbdev         if (current <= initial) {
26451c0b2f7Stbbdev             return;
26551c0b2f7Stbbdev         }
26651c0b2f7Stbbdev         initial = current;
26751c0b2f7Stbbdev     }
26851c0b2f7Stbbdev }
26951c0b2f7Stbbdev //------------------------------------------------------------------------
27051c0b2f7Stbbdev // Common requisites of the Fibonacci tests
27151c0b2f7Stbbdev //------------------------------------------------------------------------
27251c0b2f7Stbbdev 
27351c0b2f7Stbbdev const std::uintptr_t N = 20;
27451c0b2f7Stbbdev const std::uintptr_t F = 6765;
27551c0b2f7Stbbdev 
27651c0b2f7Stbbdev atomic_t g_Sum;
27751c0b2f7Stbbdev 
27851c0b2f7Stbbdev #define FIB_TEST_PROLOGUE() \
2794523a761Stbbdev     const unsigned numRepeats = g_MaxConcurrency * 4;    \
28051c0b2f7Stbbdev     utils::ConcurrencyTracker::Reset()
28151c0b2f7Stbbdev 
28251c0b2f7Stbbdev #define FIB_TEST_EPILOGUE(sum) \
2834523a761Stbbdev     CHECK(utils::ConcurrencyTracker::PeakParallelism() <= g_MaxConcurrency); \
28451c0b2f7Stbbdev     CHECK( sum == numRepeats * F );
28551c0b2f7Stbbdev 
28651c0b2f7Stbbdev 
28751c0b2f7Stbbdev // Fibonacci tasks specified as functors
28851c0b2f7Stbbdev template<class task_group_type>
28951c0b2f7Stbbdev class FibTaskBase : utils::NoAssign, utils::NoAfterlife {
29051c0b2f7Stbbdev protected:
29151c0b2f7Stbbdev     std::uintptr_t* m_pRes;
29251c0b2f7Stbbdev     mutable std::uintptr_t m_Num;
29351c0b2f7Stbbdev     virtual void impl() const = 0;
29451c0b2f7Stbbdev public:
FibTaskBase(std::uintptr_t * y,std::uintptr_t n)29551c0b2f7Stbbdev     FibTaskBase( std::uintptr_t* y, std::uintptr_t n ) : m_pRes(y), m_Num(n) {}
operator ()() const29651c0b2f7Stbbdev     void operator()() const {
29751c0b2f7Stbbdev         utils::ConcurrencyTracker ct;
29851c0b2f7Stbbdev         AssertLive();
29951c0b2f7Stbbdev         if( m_Num < 2 ) {
30051c0b2f7Stbbdev             *m_pRes = m_Num;
30151c0b2f7Stbbdev         } else {
30251c0b2f7Stbbdev             impl();
30351c0b2f7Stbbdev         }
30451c0b2f7Stbbdev     }
~FibTaskBase()30551c0b2f7Stbbdev     virtual ~FibTaskBase() {}
30651c0b2f7Stbbdev };
30751c0b2f7Stbbdev 
30851c0b2f7Stbbdev template<class task_group_type>
30951c0b2f7Stbbdev class FibTaskAsymmetricTreeWithFunctor : public FibTaskBase<task_group_type> {
31051c0b2f7Stbbdev public:
FibTaskAsymmetricTreeWithFunctor(std::uintptr_t * y,std::uintptr_t n)31151c0b2f7Stbbdev     FibTaskAsymmetricTreeWithFunctor( std::uintptr_t* y, std::uintptr_t n ) : FibTaskBase<task_group_type>(y, n) {}
impl() const31251c0b2f7Stbbdev     virtual void impl() const override {
31351c0b2f7Stbbdev         std::uintptr_t x = ~0u;
31451c0b2f7Stbbdev         task_group_type tg;
31551c0b2f7Stbbdev         tg.run( FibTaskAsymmetricTreeWithFunctor(&x, this->m_Num-1) );
31651c0b2f7Stbbdev         this->m_Num -= 2; tg.run_and_wait( *this );
31751c0b2f7Stbbdev         *(this->m_pRes) += x;
31851c0b2f7Stbbdev     }
31951c0b2f7Stbbdev };
32051c0b2f7Stbbdev 
32151c0b2f7Stbbdev template<class task_group_type>
32251c0b2f7Stbbdev class FibTaskSymmetricTreeWithFunctor : public FibTaskBase<task_group_type> {
32351c0b2f7Stbbdev public:
FibTaskSymmetricTreeWithFunctor(std::uintptr_t * y,std::uintptr_t n)32451c0b2f7Stbbdev     FibTaskSymmetricTreeWithFunctor( std::uintptr_t* y, std::uintptr_t n ) : FibTaskBase<task_group_type>(y, n) {}
impl() const32551c0b2f7Stbbdev     virtual void impl() const override {
32651c0b2f7Stbbdev         std::uintptr_t x = ~0u,
32751c0b2f7Stbbdev                y = ~0u;
32851c0b2f7Stbbdev         task_group_type tg;
32951c0b2f7Stbbdev         tg.run( FibTaskSymmetricTreeWithFunctor(&x, this->m_Num-1) );
33051c0b2f7Stbbdev         tg.run( FibTaskSymmetricTreeWithFunctor(&y, this->m_Num-2) );
33151c0b2f7Stbbdev         tg.wait();
33251c0b2f7Stbbdev         *(this->m_pRes) = x + y;
33351c0b2f7Stbbdev     }
33451c0b2f7Stbbdev };
33551c0b2f7Stbbdev 
33651c0b2f7Stbbdev // Helper functions
33751c0b2f7Stbbdev template<class fib_task>
RunFibTask(std::uintptr_t n)33851c0b2f7Stbbdev std::uintptr_t RunFibTask(std::uintptr_t n) {
33951c0b2f7Stbbdev     std::uintptr_t res = ~0u;
34051c0b2f7Stbbdev     fib_task(&res, n)();
34151c0b2f7Stbbdev     return res;
34251c0b2f7Stbbdev }
34351c0b2f7Stbbdev 
34451c0b2f7Stbbdev template<typename fib_task>
RunFibTest()34551c0b2f7Stbbdev void RunFibTest() {
34651c0b2f7Stbbdev     FIB_TEST_PROLOGUE();
34751c0b2f7Stbbdev     std::uintptr_t sum = 0;
34851c0b2f7Stbbdev     for( unsigned i = 0; i < numRepeats; ++i )
34951c0b2f7Stbbdev         sum += RunFibTask<fib_task>(N);
35051c0b2f7Stbbdev     FIB_TEST_EPILOGUE(sum);
35151c0b2f7Stbbdev }
35251c0b2f7Stbbdev 
35351c0b2f7Stbbdev template<typename fib_task>
FibFunctionNoArgs()35451c0b2f7Stbbdev void FibFunctionNoArgs() {
35551c0b2f7Stbbdev     g_Sum += RunFibTask<fib_task>(N);
35651c0b2f7Stbbdev }
35751c0b2f7Stbbdev 
35851c0b2f7Stbbdev template<typename task_group_type>
TestFibWithLambdas()35951c0b2f7Stbbdev void TestFibWithLambdas() {
36051c0b2f7Stbbdev     FIB_TEST_PROLOGUE();
36151c0b2f7Stbbdev     atomic_t sum;
36251c0b2f7Stbbdev     sum = 0;
36351c0b2f7Stbbdev     task_group_type tg;
36451c0b2f7Stbbdev     for( unsigned i = 0; i < numRepeats; ++i )
36551c0b2f7Stbbdev         tg.run( [&](){sum += RunFibTask<FibTaskSymmetricTreeWithFunctor<task_group_type> >(N);} );
36651c0b2f7Stbbdev     tg.wait();
36751c0b2f7Stbbdev     FIB_TEST_EPILOGUE(sum);
36851c0b2f7Stbbdev }
36951c0b2f7Stbbdev 
37051c0b2f7Stbbdev template<typename task_group_type>
TestFibWithFunctor()37151c0b2f7Stbbdev void TestFibWithFunctor() {
37251c0b2f7Stbbdev     RunFibTest<FibTaskAsymmetricTreeWithFunctor<task_group_type> >();
37351c0b2f7Stbbdev     RunFibTest< FibTaskSymmetricTreeWithFunctor<task_group_type> >();
37451c0b2f7Stbbdev }
37551c0b2f7Stbbdev 
37651c0b2f7Stbbdev template<typename task_group_type>
TestFibWithFunctionPtr()37751c0b2f7Stbbdev void TestFibWithFunctionPtr() {
37851c0b2f7Stbbdev     FIB_TEST_PROLOGUE();
37951c0b2f7Stbbdev     g_Sum = 0;
38051c0b2f7Stbbdev     task_group_type tg;
38151c0b2f7Stbbdev     for( unsigned i = 0; i < numRepeats; ++i )
38251c0b2f7Stbbdev         tg.run( &FibFunctionNoArgs<FibTaskSymmetricTreeWithFunctor<task_group_type> > );
38351c0b2f7Stbbdev     tg.wait();
38451c0b2f7Stbbdev     FIB_TEST_EPILOGUE(g_Sum);
38551c0b2f7Stbbdev }
38651c0b2f7Stbbdev 
38751c0b2f7Stbbdev template<typename task_group_type>
RunFibonacciTests()38851c0b2f7Stbbdev void RunFibonacciTests() {
38951c0b2f7Stbbdev     TestFibWithLambdas<task_group_type>();
39051c0b2f7Stbbdev     TestFibWithFunctor<task_group_type>();
39151c0b2f7Stbbdev     TestFibWithFunctionPtr<task_group_type>();
39251c0b2f7Stbbdev }
39351c0b2f7Stbbdev 
39451c0b2f7Stbbdev class test_exception : public std::exception
39551c0b2f7Stbbdev {
39651c0b2f7Stbbdev     const char* m_strDescription;
39751c0b2f7Stbbdev public:
test_exception(const char * descr)39851c0b2f7Stbbdev     test_exception ( const char* descr ) : m_strDescription(descr) {}
39951c0b2f7Stbbdev 
what() const40051c0b2f7Stbbdev     const char* what() const throw() override { return m_strDescription; }
40151c0b2f7Stbbdev };
40251c0b2f7Stbbdev 
4033c20b8f0SAnton Potapov using TestException = test_exception;
40451c0b2f7Stbbdev 
40551c0b2f7Stbbdev #include <string.h>
40651c0b2f7Stbbdev 
40751c0b2f7Stbbdev #define NUM_CHORES      512
40851c0b2f7Stbbdev #define NUM_GROUPS      64
40951c0b2f7Stbbdev #define SKIP_CHORES     (NUM_CHORES/4)
41051c0b2f7Stbbdev #define SKIP_GROUPS     (NUM_GROUPS/4)
41151c0b2f7Stbbdev #define EXCEPTION_DESCR1 "Test exception 1"
41251c0b2f7Stbbdev #define EXCEPTION_DESCR2 "Test exception 2"
41351c0b2f7Stbbdev 
41451c0b2f7Stbbdev atomic_t g_ExceptionCount;
41551c0b2f7Stbbdev atomic_t g_TaskCount;
41651c0b2f7Stbbdev unsigned g_ExecutedAtCancellation;
41751c0b2f7Stbbdev bool g_Rethrow;
41851c0b2f7Stbbdev bool g_Throw;
41951c0b2f7Stbbdev 
42051c0b2f7Stbbdev class ThrowingTask : utils::NoAssign, utils::NoAfterlife {
42151c0b2f7Stbbdev     atomic_t &m_TaskCount;
42251c0b2f7Stbbdev public:
ThrowingTask(atomic_t & counter)42351c0b2f7Stbbdev     ThrowingTask( atomic_t& counter ) : m_TaskCount(counter) {}
operator ()() const42451c0b2f7Stbbdev     void operator() () const {
42551c0b2f7Stbbdev         utils::ConcurrencyTracker ct;
42651c0b2f7Stbbdev         AssertLive();
42751c0b2f7Stbbdev         if ( g_Throw ) {
42851c0b2f7Stbbdev             if ( ++m_TaskCount == SKIP_CHORES )
42951c0b2f7Stbbdev                 TBB_TEST_THROW(test_exception(EXCEPTION_DESCR1));
430b15aabb3Stbbdev             utils::yield();
43151c0b2f7Stbbdev         }
43251c0b2f7Stbbdev         else {
43351c0b2f7Stbbdev             ++g_TaskCount;
43451c0b2f7Stbbdev             while( !tbb::is_current_task_group_canceling() )
435b15aabb3Stbbdev                 utils::yield();
43651c0b2f7Stbbdev         }
43751c0b2f7Stbbdev     }
43851c0b2f7Stbbdev };
43951c0b2f7Stbbdev 
ResetGlobals(bool bThrow,bool bRethrow)44051c0b2f7Stbbdev inline void ResetGlobals ( bool bThrow, bool bRethrow ) {
44151c0b2f7Stbbdev     g_Throw = bThrow;
44251c0b2f7Stbbdev     g_Rethrow = bRethrow;
44351c0b2f7Stbbdev     g_ExceptionCount = 0;
44451c0b2f7Stbbdev     g_TaskCount = 0;
44551c0b2f7Stbbdev     utils::ConcurrencyTracker::Reset();
44651c0b2f7Stbbdev }
44751c0b2f7Stbbdev 
44851c0b2f7Stbbdev template<typename task_group_type>
LaunchChildrenWithFunctor()44951c0b2f7Stbbdev void LaunchChildrenWithFunctor () {
45051c0b2f7Stbbdev     atomic_t count;
45151c0b2f7Stbbdev     count = 0;
45251c0b2f7Stbbdev     task_group_type g;
453478de5b1Stbbdev     for (unsigned i = 0; i < NUM_CHORES; ++i) {
454478de5b1Stbbdev         if (i % 2 == 1) {
455478de5b1Stbbdev             g.run(g.defer(ThrowingTask(count)));
456478de5b1Stbbdev         } else
457478de5b1Stbbdev         {
45851c0b2f7Stbbdev             g.run(ThrowingTask(count));
459478de5b1Stbbdev         }
460478de5b1Stbbdev     }
46151c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
46251c0b2f7Stbbdev     tbb::task_group_status status = tbb::not_complete;
46351c0b2f7Stbbdev     bool exceptionCaught = false;
46451c0b2f7Stbbdev     try {
46551c0b2f7Stbbdev         status = g.wait();
46651c0b2f7Stbbdev     } catch ( TestException& e ) {
46751c0b2f7Stbbdev         CHECK_MESSAGE( e.what(), "Empty what() string" );
46851c0b2f7Stbbdev         CHECK_MESSAGE( strcmp(e.what(), EXCEPTION_DESCR1) == 0, "Unknown exception" );
46951c0b2f7Stbbdev         exceptionCaught = true;
47051c0b2f7Stbbdev         ++g_ExceptionCount;
47151c0b2f7Stbbdev     } catch( ... ) { CHECK_MESSAGE( false, "Unknown exception" ); }
47251c0b2f7Stbbdev     if (g_Throw && !exceptionCaught && status != tbb::canceled) {
47351c0b2f7Stbbdev         CHECK_MESSAGE(false, "No exception in the child task group");
47451c0b2f7Stbbdev     }
47551c0b2f7Stbbdev     if ( g_Rethrow && g_ExceptionCount > SKIP_GROUPS ) {
47651c0b2f7Stbbdev         throw test_exception(EXCEPTION_DESCR2);
47751c0b2f7Stbbdev     }
47851c0b2f7Stbbdev #else
47951c0b2f7Stbbdev     g.wait();
48051c0b2f7Stbbdev #endif
48151c0b2f7Stbbdev }
48251c0b2f7Stbbdev 
48351c0b2f7Stbbdev // Tests for cancellation and exception handling behavior
48451c0b2f7Stbbdev template<typename task_group_type>
TestManualCancellationWithFunctor()48551c0b2f7Stbbdev void TestManualCancellationWithFunctor () {
48651c0b2f7Stbbdev     ResetGlobals( false, false );
48751c0b2f7Stbbdev     task_group_type tg;
488478de5b1Stbbdev     for (unsigned i = 0; i < NUM_GROUPS; ++i) {
48951c0b2f7Stbbdev         // TBB version does not require taking function address
490478de5b1Stbbdev         if (i % 2 == 0) {
491478de5b1Stbbdev             auto h = tg.defer(&LaunchChildrenWithFunctor<task_group_type>);
492478de5b1Stbbdev             tg.run(std::move(h));
493478de5b1Stbbdev         } else
494478de5b1Stbbdev         {
49551c0b2f7Stbbdev             tg.run(&LaunchChildrenWithFunctor<task_group_type>);
496478de5b1Stbbdev         }
497478de5b1Stbbdev     }
49851c0b2f7Stbbdev     CHECK_MESSAGE ( !tbb::is_current_task_group_canceling(), "Unexpected cancellation" );
49951c0b2f7Stbbdev     while ( g_MaxConcurrency > 1 && g_TaskCount == 0 )
500b15aabb3Stbbdev         utils::yield();
50151c0b2f7Stbbdev     tg.cancel();
50251c0b2f7Stbbdev     g_ExecutedAtCancellation = int(g_TaskCount);
50349e08aacStbbdev     tbb::task_group_status status = tg.wait();
50449e08aacStbbdev     CHECK_MESSAGE( status == tbb::canceled, "Task group reported invalid status." );
50551c0b2f7Stbbdev     CHECK_MESSAGE( g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken" );
50651c0b2f7Stbbdev     CHECK_MESSAGE( g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?" );
50751c0b2f7Stbbdev     CHECK_MESSAGE( g_TaskCount <= g_ExecutedAtCancellation + utils::ConcurrencyTracker::PeakParallelism(), "Too many tasks survived cancellation" );
50851c0b2f7Stbbdev }
50951c0b2f7Stbbdev 
51051c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
51151c0b2f7Stbbdev template<typename task_group_type>
TestExceptionHandling1()51251c0b2f7Stbbdev void TestExceptionHandling1 () {
51351c0b2f7Stbbdev     ResetGlobals( true, false );
51451c0b2f7Stbbdev     task_group_type tg;
51551c0b2f7Stbbdev     for( unsigned i = 0; i < NUM_GROUPS; ++i )
51651c0b2f7Stbbdev         // TBB version does not require taking function address
51751c0b2f7Stbbdev         tg.run( &LaunchChildrenWithFunctor<task_group_type> );
51851c0b2f7Stbbdev     try {
51951c0b2f7Stbbdev         tg.wait();
52051c0b2f7Stbbdev     } catch ( ... ) {
52151c0b2f7Stbbdev         CHECK_MESSAGE( false, "Unexpected exception" );
52251c0b2f7Stbbdev     }
52351c0b2f7Stbbdev     CHECK_MESSAGE( g_ExceptionCount <= NUM_GROUPS, "Too many exceptions from the child groups. The test is broken" );
52451c0b2f7Stbbdev     CHECK_MESSAGE( g_ExceptionCount == NUM_GROUPS, "Not all child groups threw the exception" );
52551c0b2f7Stbbdev }
52651c0b2f7Stbbdev 
52751c0b2f7Stbbdev template<typename task_group_type>
TestExceptionHandling2()52851c0b2f7Stbbdev void TestExceptionHandling2 () {
52951c0b2f7Stbbdev     ResetGlobals( true, true );
53051c0b2f7Stbbdev     task_group_type tg;
53151c0b2f7Stbbdev     bool exceptionCaught = false;
53251c0b2f7Stbbdev     for( unsigned i = 0; i < NUM_GROUPS; ++i ) {
53351c0b2f7Stbbdev         // TBB version does not require taking function address
53451c0b2f7Stbbdev         tg.run( &LaunchChildrenWithFunctor<task_group_type> );
53551c0b2f7Stbbdev     }
53651c0b2f7Stbbdev     try {
53751c0b2f7Stbbdev         tg.wait();
53851c0b2f7Stbbdev     } catch ( TestException& e ) {
53951c0b2f7Stbbdev         CHECK_MESSAGE( e.what(), "Empty what() string" );
54051c0b2f7Stbbdev         CHECK_MESSAGE( strcmp(e.what(), EXCEPTION_DESCR2) == 0, "Unknown exception" );
54151c0b2f7Stbbdev         exceptionCaught = true;
54251c0b2f7Stbbdev     } catch( ... ) { CHECK_MESSAGE( false, "Unknown exception" ); }
54351c0b2f7Stbbdev     CHECK_MESSAGE( exceptionCaught, "No exception thrown from the root task group" );
54451c0b2f7Stbbdev     CHECK_MESSAGE( g_ExceptionCount >= SKIP_GROUPS, "Too few exceptions from the child groups. The test is broken" );
54551c0b2f7Stbbdev     CHECK_MESSAGE( g_ExceptionCount <= NUM_GROUPS - SKIP_GROUPS, "Too many exceptions from the child groups. The test is broken" );
54651c0b2f7Stbbdev     CHECK_MESSAGE( g_ExceptionCount < NUM_GROUPS - SKIP_GROUPS, "None of the child groups was cancelled" );
54751c0b2f7Stbbdev }
54851c0b2f7Stbbdev 
54951c0b2f7Stbbdev template <typename task_group_type>
TestExceptionHandling3()55051c0b2f7Stbbdev void TestExceptionHandling3() {
55151c0b2f7Stbbdev     task_group_type tg;
55251c0b2f7Stbbdev     try {
55351c0b2f7Stbbdev         tg.run_and_wait([]() {
55451c0b2f7Stbbdev             volatile bool suppress_unreachable_code_warning = true;
55551c0b2f7Stbbdev             if (suppress_unreachable_code_warning) {
55651c0b2f7Stbbdev                 throw 1;
55751c0b2f7Stbbdev             }
55851c0b2f7Stbbdev         });
55951c0b2f7Stbbdev     } catch (int error) {
56051c0b2f7Stbbdev         CHECK(error == 1);
56151c0b2f7Stbbdev     } catch ( ... ) {
56251c0b2f7Stbbdev         CHECK_MESSAGE( false, "Unexpected exception" );
56351c0b2f7Stbbdev     }
56451c0b2f7Stbbdev }
56551c0b2f7Stbbdev 
56651c0b2f7Stbbdev template<typename task_group_type>
56751c0b2f7Stbbdev class LaunchChildrenDriver {
56851c0b2f7Stbbdev public:
Launch(task_group_type & tg)56951c0b2f7Stbbdev     void Launch(task_group_type& tg) {
57051c0b2f7Stbbdev         ResetGlobals(false, false);
57151c0b2f7Stbbdev         for (unsigned i = 0; i < NUM_GROUPS; ++i) {
57251c0b2f7Stbbdev             tg.run(LaunchChildrenWithFunctor<task_group_type>);
57351c0b2f7Stbbdev         }
57451c0b2f7Stbbdev         CHECK_MESSAGE(!tbb::is_current_task_group_canceling(), "Unexpected cancellation");
57551c0b2f7Stbbdev         while (g_MaxConcurrency > 1 && g_TaskCount == 0)
576b15aabb3Stbbdev             utils::yield();
57751c0b2f7Stbbdev     }
57851c0b2f7Stbbdev 
Finish()57951c0b2f7Stbbdev     void Finish() {
58051c0b2f7Stbbdev         CHECK_MESSAGE(g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken");
58151c0b2f7Stbbdev         CHECK_MESSAGE(g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?");
58251c0b2f7Stbbdev         CHECK_MESSAGE(g_TaskCount <= g_ExecutedAtCancellation + g_MaxConcurrency, "Too many tasks survived cancellation");
58351c0b2f7Stbbdev     }
58451c0b2f7Stbbdev }; // LaunchChildrenWithTaskHandleDriver
58551c0b2f7Stbbdev 
58651c0b2f7Stbbdev template<typename task_group_type, bool Throw>
TestMissingWait()58751c0b2f7Stbbdev void TestMissingWait () {
58851c0b2f7Stbbdev     bool exception_occurred = false,
58951c0b2f7Stbbdev          unexpected_exception = false;
59051c0b2f7Stbbdev     LaunchChildrenDriver<task_group_type> driver;
59151c0b2f7Stbbdev     try {
59251c0b2f7Stbbdev         task_group_type tg;
59351c0b2f7Stbbdev         driver.Launch( tg );
59451c0b2f7Stbbdev         volatile bool suppress_unreachable_code_warning = Throw;
59551c0b2f7Stbbdev         if (suppress_unreachable_code_warning) {
59651c0b2f7Stbbdev             throw int(); // Initiate stack unwinding
59751c0b2f7Stbbdev         }
59851c0b2f7Stbbdev     }
59951c0b2f7Stbbdev     catch ( const tbb::missing_wait& e ) {
60051c0b2f7Stbbdev         CHECK_MESSAGE( e.what(), "Error message is absent" );
60151c0b2f7Stbbdev         exception_occurred = true;
60251c0b2f7Stbbdev         unexpected_exception = Throw;
60351c0b2f7Stbbdev     }
60451c0b2f7Stbbdev     catch ( int ) {
60551c0b2f7Stbbdev         exception_occurred = true;
60651c0b2f7Stbbdev         unexpected_exception = !Throw;
60751c0b2f7Stbbdev     }
60851c0b2f7Stbbdev     catch ( ... ) {
60951c0b2f7Stbbdev         exception_occurred = unexpected_exception = true;
61051c0b2f7Stbbdev     }
61151c0b2f7Stbbdev     CHECK( exception_occurred );
61251c0b2f7Stbbdev     CHECK( !unexpected_exception );
61351c0b2f7Stbbdev     driver.Finish();
61451c0b2f7Stbbdev }
61551c0b2f7Stbbdev #endif
61651c0b2f7Stbbdev 
61751c0b2f7Stbbdev template<typename task_group_type>
RunCancellationAndExceptionHandlingTests()61851c0b2f7Stbbdev void RunCancellationAndExceptionHandlingTests() {
61951c0b2f7Stbbdev     TestManualCancellationWithFunctor<task_group_type>();
62051c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
62151c0b2f7Stbbdev     TestExceptionHandling1<task_group_type>();
62251c0b2f7Stbbdev     TestExceptionHandling2<task_group_type>();
62351c0b2f7Stbbdev     TestExceptionHandling3<task_group_type>();
62451c0b2f7Stbbdev     TestMissingWait<task_group_type, true>();
62551c0b2f7Stbbdev     TestMissingWait<task_group_type, false>();
62651c0b2f7Stbbdev #endif
62751c0b2f7Stbbdev }
62851c0b2f7Stbbdev 
EmptyFunction()62951c0b2f7Stbbdev void EmptyFunction () {}
63051c0b2f7Stbbdev 
63151c0b2f7Stbbdev struct TestFunctor {
operator ()TestFunctor63251c0b2f7Stbbdev     void operator()() { CHECK_MESSAGE( false, "Non-const operator called" ); }
operator ()TestFunctor63351c0b2f7Stbbdev     void operator()() const { /* library requires this overload only */ }
63451c0b2f7Stbbdev };
63551c0b2f7Stbbdev 
63651c0b2f7Stbbdev template<typename task_group_type>
TestConstantFunctorRequirement()63751c0b2f7Stbbdev void TestConstantFunctorRequirement() {
63851c0b2f7Stbbdev     task_group_type g;
63951c0b2f7Stbbdev     TestFunctor tf;
64051c0b2f7Stbbdev     g.run( tf ); g.wait();
64151c0b2f7Stbbdev     g.run_and_wait( tf );
64251c0b2f7Stbbdev }
64351c0b2f7Stbbdev 
64451c0b2f7Stbbdev //------------------------------------------------------------------------
64551c0b2f7Stbbdev namespace TestMoveSemanticsNS {
64651c0b2f7Stbbdev     struct TestFunctor {
operator ()TestMoveSemanticsNS::TestFunctor64751c0b2f7Stbbdev         void operator()() const {};
64851c0b2f7Stbbdev     };
64951c0b2f7Stbbdev 
65051c0b2f7Stbbdev     struct MoveOnlyFunctor : utils::MoveOnly, TestFunctor {
MoveOnlyFunctorTestMoveSemanticsNS::MoveOnlyFunctor65151c0b2f7Stbbdev         MoveOnlyFunctor() : utils::MoveOnly() {};
MoveOnlyFunctorTestMoveSemanticsNS::MoveOnlyFunctor65251c0b2f7Stbbdev         MoveOnlyFunctor(MoveOnlyFunctor&& other) : utils::MoveOnly(std::move(other)) {};
65351c0b2f7Stbbdev     };
65451c0b2f7Stbbdev 
65551c0b2f7Stbbdev     struct MovePreferableFunctor : utils::Movable, TestFunctor {
MovePreferableFunctorTestMoveSemanticsNS::MovePreferableFunctor65651c0b2f7Stbbdev         MovePreferableFunctor() : utils::Movable() {};
MovePreferableFunctorTestMoveSemanticsNS::MovePreferableFunctor65751c0b2f7Stbbdev         MovePreferableFunctor(MovePreferableFunctor&& other) : utils::Movable(std::move(other)) {};
MovePreferableFunctorTestMoveSemanticsNS::MovePreferableFunctor65851c0b2f7Stbbdev         MovePreferableFunctor(const MovePreferableFunctor& other) : utils::Movable(other) {};
65951c0b2f7Stbbdev     };
66051c0b2f7Stbbdev 
66151c0b2f7Stbbdev     struct NoMoveNoCopyFunctor : utils::NoCopy, TestFunctor {
NoMoveNoCopyFunctorTestMoveSemanticsNS::NoMoveNoCopyFunctor66251c0b2f7Stbbdev         NoMoveNoCopyFunctor() : utils::NoCopy() {};
66351c0b2f7Stbbdev         // mv ctor is not allowed as cp ctor from parent utils::NoCopy
66451c0b2f7Stbbdev     private:
66551c0b2f7Stbbdev         NoMoveNoCopyFunctor(NoMoveNoCopyFunctor&&);
66651c0b2f7Stbbdev     };
66751c0b2f7Stbbdev 
66851c0b2f7Stbbdev      template<typename task_group_type>
TestBareFunctors()66951c0b2f7Stbbdev     void TestBareFunctors() {
67051c0b2f7Stbbdev         task_group_type tg;
67151c0b2f7Stbbdev         MovePreferableFunctor mpf;
67251c0b2f7Stbbdev         // run_and_wait() doesn't have any copies or moves of arguments inside the impl
67351c0b2f7Stbbdev         tg.run_and_wait( NoMoveNoCopyFunctor() );
67451c0b2f7Stbbdev 
67551c0b2f7Stbbdev         tg.run( MoveOnlyFunctor() );
67651c0b2f7Stbbdev         tg.wait();
67751c0b2f7Stbbdev 
67851c0b2f7Stbbdev         tg.run( mpf );
67951c0b2f7Stbbdev         tg.wait();
68051c0b2f7Stbbdev         CHECK_MESSAGE(mpf.alive, "object was moved when was passed by lval");
68151c0b2f7Stbbdev         mpf.Reset();
68251c0b2f7Stbbdev 
68351c0b2f7Stbbdev         tg.run( std::move(mpf) );
68451c0b2f7Stbbdev         tg.wait();
68551c0b2f7Stbbdev         CHECK_MESSAGE(!mpf.alive, "object was copied when was passed by rval");
68651c0b2f7Stbbdev         mpf.Reset();
68751c0b2f7Stbbdev     }
68851c0b2f7Stbbdev }
68951c0b2f7Stbbdev 
69051c0b2f7Stbbdev template<typename task_group_type>
TestMoveSemantics()69151c0b2f7Stbbdev void TestMoveSemantics() {
69251c0b2f7Stbbdev     TestMoveSemanticsNS::TestBareFunctors<task_group_type>();
69351c0b2f7Stbbdev }
69451c0b2f7Stbbdev //------------------------------------------------------------------------
69551c0b2f7Stbbdev 
69651c0b2f7Stbbdev // TODO: TBB_REVAMP_TODO - enable when ETS is available
69751c0b2f7Stbbdev #if TBBTEST_USE_TBB && TBB_PREVIEW_ISOLATED_TASK_GROUP
69851c0b2f7Stbbdev namespace TestIsolationNS {
69951c0b2f7Stbbdev     class DummyFunctor {
70051c0b2f7Stbbdev     public:
DummyFunctor()70151c0b2f7Stbbdev         DummyFunctor() {}
operator ()() const70251c0b2f7Stbbdev         void operator()() const {
70351c0b2f7Stbbdev             for ( volatile int j = 0; j < 10; ++j ) {}
70451c0b2f7Stbbdev         }
70551c0b2f7Stbbdev     };
70651c0b2f7Stbbdev 
70751c0b2f7Stbbdev     template<typename task_group_type>
70851c0b2f7Stbbdev     class ParForBody {
70951c0b2f7Stbbdev         task_group_type& m_tg;
71051c0b2f7Stbbdev         std::atomic<bool>& m_preserved;
71151c0b2f7Stbbdev         tbb::enumerable_thread_specific<int>& m_ets;
71251c0b2f7Stbbdev     public:
ParForBody(task_group_type & tg,std::atomic<bool> & preserved,tbb::enumerable_thread_specific<int> & ets)71351c0b2f7Stbbdev         ParForBody(
71451c0b2f7Stbbdev             task_group_type& tg,
71551c0b2f7Stbbdev             std::atomic<bool>& preserved,
71651c0b2f7Stbbdev             tbb::enumerable_thread_specific<int>& ets
71751c0b2f7Stbbdev         ) : m_tg(tg), m_preserved(preserved), m_ets(ets) {}
71851c0b2f7Stbbdev 
operator ()(int) const71951c0b2f7Stbbdev         void operator()(int) const {
72051c0b2f7Stbbdev             if (++m_ets.local() > 1) m_preserved = false;
72151c0b2f7Stbbdev 
72251c0b2f7Stbbdev             for (int i = 0; i < 1000; ++i)
72351c0b2f7Stbbdev                 m_tg.run(DummyFunctor());
72451c0b2f7Stbbdev             m_tg.wait();
72551c0b2f7Stbbdev             m_tg.run_and_wait(DummyFunctor());
72651c0b2f7Stbbdev 
72751c0b2f7Stbbdev             --m_ets.local();
72851c0b2f7Stbbdev         }
72951c0b2f7Stbbdev     };
73051c0b2f7Stbbdev 
73151c0b2f7Stbbdev     template<typename task_group_type>
CheckIsolation(bool isolation_is_expected)73251c0b2f7Stbbdev     void CheckIsolation(bool isolation_is_expected) {
73351c0b2f7Stbbdev         task_group_type tg;
73451c0b2f7Stbbdev         std::atomic<bool> isolation_is_preserved;
73551c0b2f7Stbbdev         isolation_is_preserved = true;
73651c0b2f7Stbbdev         tbb::enumerable_thread_specific<int> ets(0);
73751c0b2f7Stbbdev 
73851c0b2f7Stbbdev         tbb::parallel_for(0, 100, ParForBody<task_group_type>(tg, isolation_is_preserved, ets));
73951c0b2f7Stbbdev 
74051c0b2f7Stbbdev         ASSERT(
74151c0b2f7Stbbdev             isolation_is_expected == isolation_is_preserved,
74251c0b2f7Stbbdev             "Actual and expected isolation-related behaviours are different"
74351c0b2f7Stbbdev         );
74451c0b2f7Stbbdev     }
74551c0b2f7Stbbdev 
74651c0b2f7Stbbdev     // Should be called only when > 1 thread is used, because otherwise isolation is guaranteed to take place
TestIsolation()74751c0b2f7Stbbdev     void TestIsolation() {
74851c0b2f7Stbbdev         CheckIsolation<tbb::task_group>(false);
74951c0b2f7Stbbdev         CheckIsolation<tbb::isolated_task_group>(true);
75051c0b2f7Stbbdev     }
75151c0b2f7Stbbdev }
75251c0b2f7Stbbdev #endif
75351c0b2f7Stbbdev 
754fcb8377bSvlserov #if __TBB_USE_ADDRESS_SANITIZER
755fcb8377bSvlserov //! Test for thread safety for the task_group
756fcb8377bSvlserov //! \brief \ref error_guessing \ref resource_usage
skip(true)757fcb8377bSvlserov TEST_CASE("Memory leaks test is not applicable under ASAN\n" * doctest::skip(true)) {}
758*7cee2251SJhaShweta1 #elif !EMSCRIPTEN
759*7cee2251SJhaShweta1 //! Emscripten requires preloading of the file used to determine memory usage, hence disabled.
76051c0b2f7Stbbdev //! Test for thread safety for the task_group
76151c0b2f7Stbbdev //! \brief \ref error_guessing \ref resource_usage
76251c0b2f7Stbbdev TEST_CASE("Thread safety test for the task group") {
763478de5b1Stbbdev     if (tbb::this_task_arena::max_concurrency() < 2) {
764478de5b1Stbbdev         // The test requires more than one thread to check thread safety
765478de5b1Stbbdev         return;
766478de5b1Stbbdev     }
76751c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
76851c0b2f7Stbbdev         if (p < 2) {
76951c0b2f7Stbbdev             continue;
77051c0b2f7Stbbdev         }
77151c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
77251c0b2f7Stbbdev         g_MaxConcurrency = p;
77351c0b2f7Stbbdev         TestThreadSafety<tbb::task_group>();
77451c0b2f7Stbbdev     }
77551c0b2f7Stbbdev }
776fcb8377bSvlserov #endif
77751c0b2f7Stbbdev 
77851c0b2f7Stbbdev //! Fibonacci test for task group
77951c0b2f7Stbbdev //! \brief \ref interface \ref requirement
78051c0b2f7Stbbdev TEST_CASE("Fibonacci test for the task group") {
78151c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
78251c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
78351c0b2f7Stbbdev         g_MaxConcurrency = p;
78451c0b2f7Stbbdev         RunFibonacciTests<tbb::task_group>();
78551c0b2f7Stbbdev     }
78651c0b2f7Stbbdev }
78751c0b2f7Stbbdev 
78851c0b2f7Stbbdev //! Cancellation and exception test for the task group
78951c0b2f7Stbbdev //! \brief \ref interface \ref requirement
79051c0b2f7Stbbdev TEST_CASE("Cancellation and exception test for the task group") {
79151c0b2f7Stbbdev     for (unsigned p = MinThread; p <= MaxThread; ++p) {
79251c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
793478de5b1Stbbdev         tbb::task_arena a(p);
79451c0b2f7Stbbdev         g_MaxConcurrency = p;
__anoneb262a490502null795478de5b1Stbbdev         a.execute([] {
79651c0b2f7Stbbdev             RunCancellationAndExceptionHandlingTests<tbb::task_group>();
797478de5b1Stbbdev         });
79851c0b2f7Stbbdev     }
79951c0b2f7Stbbdev }
80051c0b2f7Stbbdev 
80151c0b2f7Stbbdev //! Constant functor test for the task group
80251c0b2f7Stbbdev //! \brief \ref interface \ref negative
80351c0b2f7Stbbdev TEST_CASE("Constant functor test for the task group") {
80451c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
80551c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
80651c0b2f7Stbbdev         g_MaxConcurrency = p;
80751c0b2f7Stbbdev         TestConstantFunctorRequirement<tbb::task_group>();
80851c0b2f7Stbbdev     }
80951c0b2f7Stbbdev }
81051c0b2f7Stbbdev 
81151c0b2f7Stbbdev //! Move semantics test for the task group
81251c0b2f7Stbbdev //! \brief \ref interface \ref requirement
81351c0b2f7Stbbdev TEST_CASE("Move semantics test for the task group") {
81451c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
81551c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
81651c0b2f7Stbbdev         g_MaxConcurrency = p;
81751c0b2f7Stbbdev         TestMoveSemantics<tbb::task_group>();
81851c0b2f7Stbbdev     }
81951c0b2f7Stbbdev }
82051c0b2f7Stbbdev 
82151c0b2f7Stbbdev #if TBB_PREVIEW_ISOLATED_TASK_GROUP
822fcb8377bSvlserov 
823fcb8377bSvlserov #if __TBB_USE_ADDRESS_SANITIZER
824fcb8377bSvlserov //! Test for thread safety for the isolated_task_group
825fcb8377bSvlserov //! \brief \ref error_guessing
skip(true)826fcb8377bSvlserov TEST_CASE("Memory leaks test is not applicable under ASAN\n" * doctest::skip(true)) {}
827*7cee2251SJhaShweta1 #elif !EMSCRIPTEN
82851c0b2f7Stbbdev //! Test for thread safety for the isolated_task_group
82951c0b2f7Stbbdev //! \brief \ref error_guessing
83051c0b2f7Stbbdev TEST_CASE("Thread safety test for the isolated task group") {
831478de5b1Stbbdev     if (tbb::this_task_arena::max_concurrency() < 2) {
832478de5b1Stbbdev         // The test requires more than one thread to check thread safety
833478de5b1Stbbdev         return;
834478de5b1Stbbdev     }
83551c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
83651c0b2f7Stbbdev         if (p < 2) {
83751c0b2f7Stbbdev             continue;
83851c0b2f7Stbbdev         }
83951c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
84051c0b2f7Stbbdev         g_MaxConcurrency = p;
84151c0b2f7Stbbdev         TestThreadSafety<tbb::isolated_task_group>();
84251c0b2f7Stbbdev     }
84351c0b2f7Stbbdev }
844fcb8377bSvlserov #endif
84551c0b2f7Stbbdev 
84651c0b2f7Stbbdev //! Cancellation and exception test for the isolated task group
84751c0b2f7Stbbdev //! \brief \ref interface \ref requirement
84851c0b2f7Stbbdev TEST_CASE("Fibonacci test for the isolated task group") {
84951c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
85051c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
85151c0b2f7Stbbdev         g_MaxConcurrency = p;
85251c0b2f7Stbbdev         RunFibonacciTests<tbb::isolated_task_group>();
85351c0b2f7Stbbdev     }
85451c0b2f7Stbbdev }
85551c0b2f7Stbbdev 
85651c0b2f7Stbbdev //! Cancellation and exception test for the isolated task group
85751c0b2f7Stbbdev //! \brief \ref interface \ref requirement
85851c0b2f7Stbbdev TEST_CASE("Cancellation and exception test for the isolated task group") {
85951c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
86051c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
86151c0b2f7Stbbdev         g_MaxConcurrency = p;
86251c0b2f7Stbbdev         RunCancellationAndExceptionHandlingTests<tbb::isolated_task_group>();
86351c0b2f7Stbbdev     }
86451c0b2f7Stbbdev }
86551c0b2f7Stbbdev 
86651c0b2f7Stbbdev //! Constant functor test for the isolated task group.
86751c0b2f7Stbbdev //! \brief \ref interface \ref negative
86851c0b2f7Stbbdev TEST_CASE("Constant functor test for the isolated task group") {
86951c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
87051c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
87151c0b2f7Stbbdev         g_MaxConcurrency = p;
87251c0b2f7Stbbdev         TestConstantFunctorRequirement<tbb::isolated_task_group>();
87351c0b2f7Stbbdev     }
87451c0b2f7Stbbdev }
87551c0b2f7Stbbdev 
87651c0b2f7Stbbdev //! Move semantics test for the isolated task group.
87751c0b2f7Stbbdev //! \brief \ref interface \ref requirement
87851c0b2f7Stbbdev TEST_CASE("Move semantics test for the isolated task group") {
87951c0b2f7Stbbdev     for (unsigned p=MinThread; p <= MaxThread; ++p) {
88051c0b2f7Stbbdev         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
88151c0b2f7Stbbdev         g_MaxConcurrency = p;
88251c0b2f7Stbbdev         TestMoveSemantics<tbb::isolated_task_group>();
88351c0b2f7Stbbdev     }
88451c0b2f7Stbbdev }
88574b7fc74SAnton Potapov 
88674b7fc74SAnton Potapov //TODO: add test void isolated_task_group::run(d2::task_handle&& h) and isolated_task_group::::run_and_wait(d2::task_handle&& h)
88751c0b2f7Stbbdev #endif /* TBB_PREVIEW_ISOLATED_TASK_GROUP */
88851c0b2f7Stbbdev 
run_deep_stealing(tbb::task_group & tg1,tbb::task_group & tg2,int num_tasks,std::atomic<int> & tasks_executed)88951c0b2f7Stbbdev void run_deep_stealing(tbb::task_group& tg1, tbb::task_group& tg2, int num_tasks, std::atomic<int>& tasks_executed) {
89051c0b2f7Stbbdev     for (int i = 0; i < num_tasks; ++i) {
89151c0b2f7Stbbdev         tg2.run([&tg1, &tasks_executed] {
89251c0b2f7Stbbdev             volatile char consume_stack[1000]{};
89351c0b2f7Stbbdev             ++tasks_executed;
89451c0b2f7Stbbdev             tg1.wait();
89551c0b2f7Stbbdev             utils::suppress_unused_warning(consume_stack);
89651c0b2f7Stbbdev         });
89751c0b2f7Stbbdev     }
89851c0b2f7Stbbdev }
89951c0b2f7Stbbdev 
90051c0b2f7Stbbdev // TODO: move to the conformance test
90151c0b2f7Stbbdev //! Test for stack overflow avoidance mechanism.
90251c0b2f7Stbbdev //! \brief \ref requirement
90351c0b2f7Stbbdev TEST_CASE("Test for stack overflow avoidance mechanism") {
90451c0b2f7Stbbdev     if (tbb::this_task_arena::max_concurrency() < 2) {
90551c0b2f7Stbbdev         return;
90651c0b2f7Stbbdev     }
90751c0b2f7Stbbdev 
90851c0b2f7Stbbdev     tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2);
90951c0b2f7Stbbdev     tbb::task_group tg1;
91051c0b2f7Stbbdev     tbb::task_group tg2;
91151c0b2f7Stbbdev     std::atomic<int> tasks_executed{};
__anoneb262a490702null91251c0b2f7Stbbdev     tg1.run_and_wait([&tg1, &tg2, &tasks_executed] {
91351c0b2f7Stbbdev         run_deep_stealing(tg1, tg2, 10000, tasks_executed);
91451c0b2f7Stbbdev         while (tasks_executed < 100) {
91551c0b2f7Stbbdev             // Some stealing is expected to happen.
916b15aabb3Stbbdev             utils::yield();
91751c0b2f7Stbbdev         }
91851c0b2f7Stbbdev         CHECK(tasks_executed < 10000);
91951c0b2f7Stbbdev     });
92051c0b2f7Stbbdev     tg2.wait();
92151c0b2f7Stbbdev     CHECK(tasks_executed == 10000);
92251c0b2f7Stbbdev }
92351c0b2f7Stbbdev 
92451c0b2f7Stbbdev //! Test for stack overflow avoidance mechanism.
92551c0b2f7Stbbdev //! \brief \ref error_guessing
92651c0b2f7Stbbdev TEST_CASE("Test for stack overflow avoidance mechanism within arena") {
92751c0b2f7Stbbdev     if (tbb::this_task_arena::max_concurrency() < 2) {
92851c0b2f7Stbbdev         return;
92951c0b2f7Stbbdev     }
93051c0b2f7Stbbdev 
931c4568449SPavel Kumbrasev     tbb::task_arena a1(2, 1);
__anoneb262a490802null932c4568449SPavel Kumbrasev     a1.execute([] {
93351c0b2f7Stbbdev         tbb::task_group tg1;
93451c0b2f7Stbbdev         tbb::task_group tg2;
93551c0b2f7Stbbdev         std::atomic<int> tasks_executed{};
93651c0b2f7Stbbdev 
93751c0b2f7Stbbdev         // Determine nested task execution limit.
93851c0b2f7Stbbdev         int second_thread_executed{};
93951c0b2f7Stbbdev         tg1.run_and_wait([&tg1, &tg2, &tasks_executed, &second_thread_executed] {
94051c0b2f7Stbbdev             run_deep_stealing(tg1, tg2, 10000, tasks_executed);
94151c0b2f7Stbbdev             do {
94251c0b2f7Stbbdev                 second_thread_executed = tasks_executed;
94351c0b2f7Stbbdev                 utils::Sleep(10);
94451c0b2f7Stbbdev             } while (second_thread_executed < 100 || second_thread_executed != tasks_executed);
94551c0b2f7Stbbdev             CHECK(tasks_executed < 10000);
94651c0b2f7Stbbdev         });
94751c0b2f7Stbbdev         tg2.wait();
94851c0b2f7Stbbdev         CHECK(tasks_executed == 10000);
94951c0b2f7Stbbdev 
95051c0b2f7Stbbdev         tasks_executed = 0;
951c4568449SPavel Kumbrasev         tbb::task_arena a2(2, 2);
952c4568449SPavel Kumbrasev         tg1.run_and_wait([&a2, &tg1, &tg2, &tasks_executed, second_thread_executed] {
95351c0b2f7Stbbdev             run_deep_stealing(tg1, tg2, second_thread_executed - 1, tasks_executed);
95451c0b2f7Stbbdev             while (tasks_executed < second_thread_executed - 1) {
95551c0b2f7Stbbdev                 // Wait until the second thread near the limit.
956b15aabb3Stbbdev                 utils::yield();
95751c0b2f7Stbbdev             }
958c4568449SPavel Kumbrasev             tg2.run([&a2, &tg1, &tasks_executed] {
959c4568449SPavel Kumbrasev                 a2.execute([&tg1, &tasks_executed] {
96051c0b2f7Stbbdev                     volatile char consume_stack[1000]{};
96151c0b2f7Stbbdev                     ++tasks_executed;
96251c0b2f7Stbbdev                     tg1.wait();
96351c0b2f7Stbbdev                     utils::suppress_unused_warning(consume_stack);
96451c0b2f7Stbbdev                 });
96551c0b2f7Stbbdev             });
96651c0b2f7Stbbdev             while (tasks_executed < second_thread_executed) {
96751c0b2f7Stbbdev                 // Wait until the second joins the arena.
968b15aabb3Stbbdev                 utils::yield();
96951c0b2f7Stbbdev             }
970c4568449SPavel Kumbrasev             a2.execute([&tg1, &tg2, &tasks_executed] {
97151c0b2f7Stbbdev                 run_deep_stealing(tg1, tg2, 10000, tasks_executed);
97251c0b2f7Stbbdev             });
97351c0b2f7Stbbdev             int currently_executed{};
97451c0b2f7Stbbdev             do {
97551c0b2f7Stbbdev                 currently_executed = tasks_executed;
97651c0b2f7Stbbdev                 utils::Sleep(10);
97751c0b2f7Stbbdev             } while (currently_executed != tasks_executed);
97851c0b2f7Stbbdev             CHECK(tasks_executed < 10000 + second_thread_executed);
97951c0b2f7Stbbdev         });
980c4568449SPavel Kumbrasev         a2.execute([&tg2] {
98151c0b2f7Stbbdev             tg2.wait();
98251c0b2f7Stbbdev         });
98351c0b2f7Stbbdev         CHECK(tasks_executed == 10000 + second_thread_executed);
984c4568449SPavel Kumbrasev     });
98551c0b2f7Stbbdev }
98651c0b2f7Stbbdev 
98751c0b2f7Stbbdev //! Test checks that we can submit work to task_group asynchronously with waiting.
98851c0b2f7Stbbdev //! \brief \ref regression
98951c0b2f7Stbbdev TEST_CASE("Async task group") {
99051c0b2f7Stbbdev     int num_threads = tbb::this_task_arena::max_concurrency();
991478de5b1Stbbdev     if (num_threads < 3) {
992478de5b1Stbbdev         // The test requires at least 2 worker threads
993478de5b1Stbbdev         return;
994478de5b1Stbbdev     }
99551c0b2f7Stbbdev     tbb::task_arena a(2*num_threads, num_threads);
99651c0b2f7Stbbdev     utils::SpinBarrier barrier(num_threads + 2);
99751c0b2f7Stbbdev     tbb::task_group tg[2];
99851c0b2f7Stbbdev     std::atomic<bool> finished[2]{};
99951c0b2f7Stbbdev     finished[0] = false; finished[1] = false;
100051c0b2f7Stbbdev     for (int i = 0; i < 2; ++i) {
__anoneb262a490f02null100151c0b2f7Stbbdev         a.enqueue([i, &tg, &finished, &barrier] {
100251c0b2f7Stbbdev             barrier.wait();
100351c0b2f7Stbbdev             for (int j = 0; j < 10000; ++j) {
100451c0b2f7Stbbdev                 tg[i].run([] {});
1005b15aabb3Stbbdev                 utils::yield();
100651c0b2f7Stbbdev             }
100751c0b2f7Stbbdev             finished[i] = true;
100851c0b2f7Stbbdev         });
100951c0b2f7Stbbdev     }
__anoneb262a491102(int idx) 101051c0b2f7Stbbdev     utils::NativeParallelFor(num_threads, [&](int idx) {
101151c0b2f7Stbbdev         barrier.wait();
101251c0b2f7Stbbdev         a.execute([idx, &tg, &finished] {
10138dcbd5b1Stbbdev             std::size_t counter{};
101451c0b2f7Stbbdev             while (!finished[idx%2]) {
101551c0b2f7Stbbdev                 tg[idx%2].wait();
1016b15aabb3Stbbdev                 if (counter++ % 16 == 0) utils::yield();
101751c0b2f7Stbbdev             }
101851c0b2f7Stbbdev             tg[idx%2].wait();
101951c0b2f7Stbbdev         });
102051c0b2f7Stbbdev     });
102151c0b2f7Stbbdev }
1022478de5b1Stbbdev 
1023478de5b1Stbbdev struct SelfRunner {
1024478de5b1Stbbdev     tbb::task_group& m_tg;
1025478de5b1Stbbdev     std::atomic<unsigned>& count;
operator ()SelfRunner1026478de5b1Stbbdev     void operator()() const {
1027478de5b1Stbbdev         unsigned previous_count = count.fetch_sub(1);
1028478de5b1Stbbdev         if (previous_count > 1)
1029478de5b1Stbbdev             m_tg.run( *this );
1030478de5b1Stbbdev     }
1031478de5b1Stbbdev };
1032478de5b1Stbbdev 
1033478de5b1Stbbdev //! Submit work to single task_group instance from inside the work
1034478de5b1Stbbdev //! \brief \ref error_guessing
1035478de5b1Stbbdev TEST_CASE("Run self using same task_group instance") {
1036478de5b1Stbbdev     const unsigned num = 10;
1037478de5b1Stbbdev     std::atomic<unsigned> count{num};
1038478de5b1Stbbdev     tbb::task_group tg;
1039478de5b1Stbbdev     SelfRunner uf{tg, count};
1040478de5b1Stbbdev     tg.run( uf );
1041478de5b1Stbbdev     tg.wait();
1042478de5b1Stbbdev     CHECK_MESSAGE(
1043478de5b1Stbbdev         count == 0,
1044478de5b1Stbbdev         "Not all tasks were spawned from inside the functor running within task_group."
1045478de5b1Stbbdev     );
1046478de5b1Stbbdev }
1047478de5b1Stbbdev 
104874b7fc74SAnton Potapov //TODO: move to some common place to share with conformance tests
1049478de5b1Stbbdev namespace accept_task_group_context {
1050478de5b1Stbbdev 
1051478de5b1Stbbdev template <typename TaskGroup, typename CancelF, typename WaitF>
run_cancellation_use_case(CancelF && cancel,WaitF && wait)1052478de5b1Stbbdev void run_cancellation_use_case(CancelF&& cancel, WaitF&& wait) {
1053478de5b1Stbbdev     std::atomic<bool> outer_cancelled{false};
1054478de5b1Stbbdev     std::atomic<unsigned> count{13};
1055478de5b1Stbbdev 
1056478de5b1Stbbdev     tbb::task_group_context inner_ctx(tbb::task_group_context::isolated);
1057478de5b1Stbbdev     TaskGroup inner_tg(inner_ctx);
1058478de5b1Stbbdev 
1059478de5b1Stbbdev     tbb::task_group outer_tg;
1060478de5b1Stbbdev     auto outer_tg_task = [&] {
1061478de5b1Stbbdev         inner_tg.run([&] {
1062478de5b1Stbbdev             utils::SpinWaitUntilEq(outer_cancelled, true);
1063478de5b1Stbbdev             inner_tg.run( SelfRunner{inner_tg, count} );
1064478de5b1Stbbdev         });
1065478de5b1Stbbdev 
1066478de5b1Stbbdev         utils::try_call([&] {
1067478de5b1Stbbdev             std::forward<CancelF>(cancel)(outer_tg);
1068478de5b1Stbbdev         }).on_completion([&] {
1069478de5b1Stbbdev             outer_cancelled = true;
1070478de5b1Stbbdev         });
1071478de5b1Stbbdev     };
1072478de5b1Stbbdev 
1073478de5b1Stbbdev     auto check = [&] {
1074478de5b1Stbbdev         tbb::task_group_status outer_status = tbb::task_group_status::not_complete;
1075478de5b1Stbbdev         outer_status = std::forward<WaitF>(wait)(outer_tg);
1076478de5b1Stbbdev         CHECK_MESSAGE(
1077478de5b1Stbbdev             outer_status == tbb::task_group_status::canceled,
1078478de5b1Stbbdev             "Outer task group should have been cancelled."
1079478de5b1Stbbdev         );
1080478de5b1Stbbdev 
1081478de5b1Stbbdev         tbb::task_group_status inner_status = inner_tg.wait();
1082478de5b1Stbbdev         CHECK_MESSAGE(
1083478de5b1Stbbdev             inner_status == tbb::task_group_status::complete,
1084478de5b1Stbbdev             "Inner task group should have completed despite the cancellation of the outer one."
1085478de5b1Stbbdev         );
1086478de5b1Stbbdev 
1087478de5b1Stbbdev         CHECK_MESSAGE(0 == count, "Some of the inner group tasks were not executed.");
1088478de5b1Stbbdev     };
1089478de5b1Stbbdev 
1090478de5b1Stbbdev     outer_tg.run(outer_tg_task);
1091478de5b1Stbbdev     check();
1092478de5b1Stbbdev }
1093478de5b1Stbbdev 
1094478de5b1Stbbdev template <typename TaskGroup>
test()1095478de5b1Stbbdev void test() {
1096478de5b1Stbbdev     run_cancellation_use_case<TaskGroup>(
1097478de5b1Stbbdev         [](tbb::task_group& outer) { outer.cancel(); },
1098478de5b1Stbbdev         [](tbb::task_group& outer) { return outer.wait(); }
1099478de5b1Stbbdev     );
1100478de5b1Stbbdev 
1101478de5b1Stbbdev #if TBB_USE_EXCEPTIONS
1102478de5b1Stbbdev     run_cancellation_use_case<TaskGroup>(
1103478de5b1Stbbdev         [](tbb::task_group& /*outer*/) {
1104478de5b1Stbbdev             volatile bool suppress_unreachable_code_warning = true;
1105478de5b1Stbbdev             if (suppress_unreachable_code_warning) {
1106478de5b1Stbbdev                 throw int();
1107478de5b1Stbbdev             }
1108478de5b1Stbbdev         },
1109478de5b1Stbbdev         [](tbb::task_group& outer) {
1110478de5b1Stbbdev             try {
1111478de5b1Stbbdev                 outer.wait();
1112478de5b1Stbbdev                 return tbb::task_group_status::complete;
1113478de5b1Stbbdev             } catch(const int&) {
1114478de5b1Stbbdev                 return tbb::task_group_status::canceled;
1115478de5b1Stbbdev             }
1116478de5b1Stbbdev         }
1117478de5b1Stbbdev     );
1118478de5b1Stbbdev #endif
1119478de5b1Stbbdev }
1120478de5b1Stbbdev 
1121478de5b1Stbbdev } // namespace accept_task_group_context
1122478de5b1Stbbdev 
1123478de5b1Stbbdev //! Respect task_group_context passed from outside
1124478de5b1Stbbdev //! \brief \ref interface \ref requirement
1125478de5b1Stbbdev TEST_CASE("Respect task_group_context passed from outside") {
1126478de5b1Stbbdev #if TBB_PREVIEW_ISOLATED_TASK_GROUP
1127478de5b1Stbbdev     accept_task_group_context::test<tbb::isolated_task_group>();
1128478de5b1Stbbdev #endif
1129478de5b1Stbbdev }
1130478de5b1Stbbdev 
1131478de5b1Stbbdev #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1132478de5b1Stbbdev //! The test for task_handle inside other task waiting with run
1133478de5b1Stbbdev //! \brief \ref requirement
1134478de5b1Stbbdev TEST_CASE("Task handle for scheduler bypass"){
1135478de5b1Stbbdev     tbb::task_group tg;
1136478de5b1Stbbdev     std::atomic<bool> run {false};
1137478de5b1Stbbdev 
__anoneb262a491c02null1138478de5b1Stbbdev     tg.run([&]{
1139478de5b1Stbbdev         return tg.defer([&]{
1140478de5b1Stbbdev             run = true;
1141478de5b1Stbbdev         });
1142478de5b1Stbbdev     });
1143478de5b1Stbbdev 
1144478de5b1Stbbdev     tg.wait();
1145478de5b1Stbbdev     CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run");
1146478de5b1Stbbdev }
1147478de5b1Stbbdev 
1148478de5b1Stbbdev //! The test for task_handle inside other task waiting with run_and_wait
1149478de5b1Stbbdev //! \brief \ref requirement
1150478de5b1Stbbdev TEST_CASE("Task handle for scheduler bypass via run_and_wait"){
1151478de5b1Stbbdev     tbb::task_group tg;
1152478de5b1Stbbdev     std::atomic<bool> run {false};
1153478de5b1Stbbdev 
__anoneb262a491e02null1154478de5b1Stbbdev     tg.run_and_wait([&]{
1155478de5b1Stbbdev         return tg.defer([&]{
1156478de5b1Stbbdev             run = true;
1157478de5b1Stbbdev         });
1158478de5b1Stbbdev     });
1159478de5b1Stbbdev 
1160478de5b1Stbbdev     CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run");
1161478de5b1Stbbdev }
116274b7fc74SAnton Potapov #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1163478de5b1Stbbdev 
1164478de5b1Stbbdev #if TBB_USE_EXCEPTIONS
116574b7fc74SAnton Potapov //As these tests are against behavior marked by spec as undefined, they can not be put into conformance tests
1166478de5b1Stbbdev 
1167478de5b1Stbbdev //! The test for error in scheduling empty task_handle
1168478de5b1Stbbdev //! \brief \ref requirement
116974b7fc74SAnton Potapov TEST_CASE("Empty task_handle cannot be scheduled"
should_fail()117074b7fc74SAnton Potapov         * doctest::should_fail()    //Test needs to revised as implementation uses assertions instead of exceptions
117174b7fc74SAnton Potapov         * doctest::skip()           //skip the test for now, to not pollute the test log
117274b7fc74SAnton Potapov ){
1173478de5b1Stbbdev     tbb::task_group tg;
1174478de5b1Stbbdev 
1175478de5b1Stbbdev     CHECK_THROWS_WITH_AS(tg.run(tbb::task_handle{}), "Attempt to schedule empty task_handle", std::runtime_error);
1176478de5b1Stbbdev }
1177478de5b1Stbbdev 
1178478de5b1Stbbdev //! The test for error in task_handle being scheduled into task_group different from one it was created from
1179478de5b1Stbbdev //! \brief \ref requirement
118074b7fc74SAnton Potapov TEST_CASE("task_handle cannot be scheduled into different task_group"
should_fail()118174b7fc74SAnton Potapov         * doctest::should_fail()    //Test needs to revised as implementation uses assertions instead of exceptions
118274b7fc74SAnton Potapov         * doctest::skip()           //skip the test for now, to not pollute the test log
118374b7fc74SAnton Potapov ){
1184478de5b1Stbbdev     tbb::task_group tg;
1185478de5b1Stbbdev     tbb::task_group tg1;
1186478de5b1Stbbdev 
1187478de5b1Stbbdev     CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error);
1188478de5b1Stbbdev }
1189478de5b1Stbbdev 
1190478de5b1Stbbdev //! The test for error in task_handle being scheduled into task_group different from one it was created from
1191478de5b1Stbbdev //! \brief \ref requirement
1192478de5b1Stbbdev TEST_CASE("task_handle cannot be scheduled into other task_group of the same context"
should_fail()1193dd8f8a78SAnton Potapov         * doctest::should_fail()    //Implementation is no there yet, as it is not clear that is the expected behavior
1194478de5b1Stbbdev         * doctest::skip()           //skip the test for now, to not pollute the test log
1195478de5b1Stbbdev )
1196478de5b1Stbbdev {
1197478de5b1Stbbdev     tbb::task_group_context ctx;
1198478de5b1Stbbdev 
1199478de5b1Stbbdev     tbb::task_group tg(ctx);
1200478de5b1Stbbdev     tbb::task_group tg1(ctx);
1201478de5b1Stbbdev 
1202478de5b1Stbbdev     CHECK_NOTHROW(tg.run(tg.defer([]{})));
1203478de5b1Stbbdev     CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error);
1204478de5b1Stbbdev }
1205478de5b1Stbbdev 
1206478de5b1Stbbdev #endif // TBB_USE_EXCEPTIONS
120774b7fc74SAnton Potapov 
1208