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