xref: /oneTBB/test/tbb/test_task_group.cpp (revision 5d8ddb3f)
1 /*
2     Copyright (c) 2005-2021 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #if __TBB_CPF_BUILD
18 #define TBB_PREVIEW_ISOLATED_TASK_GROUP 1
19 #define TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1
20 #endif
21 
22 #include "common/test.h"
23 #include "common/utils.h"
24 #include "oneapi/tbb/detail/_config.h"
25 #include "tbb/global_control.h"
26 
27 #include "tbb/task_group.h"
28 
29 #include "common/concurrency_tracker.h"
30 
31 #include <atomic>
32 #include <stdexcept>
33 
34 //! \file test_task_group.cpp
35 //! \brief Test for [scheduler.task_group scheduler.task_group_status] specification
36 
37 unsigned g_MaxConcurrency = 4;
38 using atomic_t = std::atomic<std::uintptr_t>;
39 unsigned MinThread = 1;
40 unsigned MaxThread = 4;
41 
42 //------------------------------------------------------------------------
43 // Tests for the thread safety of the task_group manipulations
44 //------------------------------------------------------------------------
45 
46 #include "common/spin_barrier.h"
47 
48 enum SharingMode {
49     VagabondGroup = 1,
50     ParallelWait = 2
51 };
52 
53 template<typename task_group_type>
54 class SharedGroupBodyImpl : utils::NoCopy, utils::NoAfterlife {
55     static const std::uintptr_t c_numTasks0 = 4096,
56                         c_numTasks1 = 1024;
57 
58     const std::uintptr_t m_numThreads;
59     const std::uintptr_t m_sharingMode;
60 
61     task_group_type *m_taskGroup;
62     atomic_t m_tasksSpawned,
63              m_threadsReady;
64     utils::SpinBarrier m_barrier;
65 
66     static atomic_t s_tasksExecuted;
67 
68     struct TaskFunctor {
69         SharedGroupBodyImpl *m_pOwner;
70         void operator () () const {
71             if ( m_pOwner->m_sharingMode & ParallelWait ) {
72                 while ( utils::ConcurrencyTracker::PeakParallelism() < m_pOwner->m_numThreads )
73                     utils::yield();
74             }
75             ++s_tasksExecuted;
76         }
77     };
78 
79     TaskFunctor m_taskFunctor;
80 
81     void Spawn ( std::uintptr_t numTasks ) {
82         for ( std::uintptr_t i = 0; i < numTasks; ++i ) {
83             ++m_tasksSpawned;
84             utils::ConcurrencyTracker ct;
85             m_taskGroup->run( m_taskFunctor );
86         }
87         ++m_threadsReady;
88     }
89 
90     void DeleteTaskGroup () {
91         delete m_taskGroup;
92         m_taskGroup = NULL;
93     }
94 
95     void Wait () {
96         while ( m_threadsReady != m_numThreads )
97             utils::yield();
98         const std::uintptr_t numSpawned = c_numTasks0 + c_numTasks1 * (m_numThreads - 1);
99         CHECK_MESSAGE( m_tasksSpawned == numSpawned, "Wrong number of spawned tasks. The test is broken" );
100         INFO("Max spawning parallelism is " << utils::ConcurrencyTracker::PeakParallelism() << "out of " << g_MaxConcurrency);
101         if ( m_sharingMode & ParallelWait ) {
102             m_barrier.wait( &utils::ConcurrencyTracker::Reset );
103             {
104                 utils::ConcurrencyTracker ct;
105                 m_taskGroup->wait();
106             }
107             if ( utils::ConcurrencyTracker::PeakParallelism() == 1 )
108                 WARN( "Warning: No parallel waiting detected in TestParallelWait" );
109             m_barrier.wait();
110         }
111         else
112             m_taskGroup->wait();
113         CHECK_MESSAGE( m_tasksSpawned == numSpawned, "No tasks should be spawned after wait starts. The test is broken" );
114         CHECK_MESSAGE( s_tasksExecuted == numSpawned, "Not all spawned tasks were executed" );
115     }
116 
117 public:
118     SharedGroupBodyImpl ( std::uintptr_t numThreads, std::uintptr_t sharingMode = 0 )
119         : m_numThreads(numThreads)
120         , m_sharingMode(sharingMode)
121         , m_taskGroup(NULL)
122         , m_barrier(numThreads)
123     {
124         CHECK_MESSAGE( m_numThreads > 1, "SharedGroupBody tests require concurrency" );
125         if ((m_sharingMode & VagabondGroup) && m_numThreads != 2) {
126             CHECK_MESSAGE(false, "In vagabond mode SharedGroupBody must be used with 2 threads only");
127         }
128         utils::ConcurrencyTracker::Reset();
129         s_tasksExecuted = 0;
130         m_tasksSpawned = 0;
131         m_threadsReady = 0;
132         m_taskFunctor.m_pOwner = this;
133     }
134 
135     void Run ( std::uintptr_t idx ) {
136         AssertLive();
137         if ( idx == 0 ) {
138             if (m_taskGroup || m_tasksSpawned) {
139                 CHECK_MESSAGE(false, "SharedGroupBody must be reset before reuse");
140             }
141             m_taskGroup = new task_group_type;
142             Spawn( c_numTasks0 );
143             Wait();
144             if ( m_sharingMode & VagabondGroup )
145                 m_barrier.wait();
146             else
147                 DeleteTaskGroup();
148         }
149         else {
150             while ( m_tasksSpawned == 0 )
151                 utils::yield();
152             CHECK_MESSAGE ( m_taskGroup, "Task group is not initialized");
153             Spawn (c_numTasks1);
154             if ( m_sharingMode & ParallelWait )
155                 Wait();
156             if ( m_sharingMode & VagabondGroup ) {
157                 CHECK_MESSAGE ( idx == 1, "In vagabond mode SharedGroupBody must be used with 2 threads only" );
158                 m_barrier.wait();
159                 DeleteTaskGroup();
160             }
161         }
162         AssertLive();
163     }
164 };
165 
166 template<typename task_group_type>
167 atomic_t SharedGroupBodyImpl<task_group_type>::s_tasksExecuted;
168 
169 template<typename task_group_type>
170 class  SharedGroupBody : utils::NoAssign, utils::NoAfterlife {
171     bool m_bOwner;
172     SharedGroupBodyImpl<task_group_type> *m_pImpl;
173 public:
174     SharedGroupBody ( std::uintptr_t numThreads, std::uintptr_t sharingMode = 0 )
175         : utils::NoAssign()
176         , utils::NoAfterlife()
177         , m_bOwner(true)
178         , m_pImpl( new SharedGroupBodyImpl<task_group_type>(numThreads, sharingMode) )
179     {}
180     SharedGroupBody ( const SharedGroupBody& src )
181         : utils::NoAssign()
182         , utils::NoAfterlife()
183         , m_bOwner(false)
184         , m_pImpl(src.m_pImpl)
185     {}
186     ~SharedGroupBody () {
187         if ( m_bOwner )
188             delete m_pImpl;
189     }
190     void operator() ( std::uintptr_t idx ) const {
191         // Wrap the functior into additional task group to enforce bounding.
192         task_group_type tg;
193         tg.run_and_wait([&] { m_pImpl->Run(idx); });
194     }
195 };
196 
197 template<typename task_group_type>
198 class RunAndWaitSyncronizationTestBody : utils::NoAssign {
199     utils::SpinBarrier& m_barrier;
200     std::atomic<bool>& m_completed;
201     task_group_type& m_tg;
202 public:
203     RunAndWaitSyncronizationTestBody(utils::SpinBarrier& barrier, std::atomic<bool>& completed, task_group_type& tg)
204         : m_barrier(barrier), m_completed(completed), m_tg(tg) {}
205 
206     void operator()() const {
207         m_barrier.wait();
208         utils::doDummyWork(100000);
209         m_completed = true;
210     }
211 
212     void operator()(int id) const {
213         if (id == 0) {
214             m_tg.run_and_wait(*this);
215         } else {
216             m_barrier.wait();
217             m_tg.wait();
218             CHECK_MESSAGE(m_completed, "A concurrent waiter has left the wait method earlier than work has finished");
219         }
220     }
221 };
222 
223 template<typename task_group_type>
224 void TestParallelSpawn () {
225     NativeParallelFor( g_MaxConcurrency, SharedGroupBody<task_group_type>(g_MaxConcurrency) );
226 }
227 
228 template<typename task_group_type>
229 void TestParallelWait () {
230     NativeParallelFor( g_MaxConcurrency, SharedGroupBody<task_group_type>(g_MaxConcurrency, ParallelWait) );
231 
232     utils::SpinBarrier barrier(g_MaxConcurrency);
233     std::atomic<bool> completed;
234     completed = false;
235     task_group_type tg;
236     RunAndWaitSyncronizationTestBody<task_group_type> b(barrier, completed, tg);
237     NativeParallelFor( g_MaxConcurrency, b );
238 }
239 
240 // Tests non-stack-bound task group (the group that is allocated by one thread and destroyed by the other)
241 template<typename task_group_type>
242 void TestVagabondGroup () {
243     NativeParallelFor( 2, SharedGroupBody<task_group_type>(2, VagabondGroup) );
244 }
245 
246 #include "common/memory_usage.h"
247 
248 template<typename task_group_type>
249 void TestThreadSafety() {
250     auto tests = [] {
251         for (int trail = 0; trail < 10; ++trail) {
252             TestParallelSpawn<task_group_type>();
253             TestParallelWait<task_group_type>();
254             TestVagabondGroup<task_group_type>();
255         }
256     };
257 
258     // Test and warm up allocator.
259     tests();
260 
261     // Ensure that cosumption is stabilized.
262     std::size_t initial = utils::GetMemoryUsage();
263     for (;;) {
264         tests();
265         std::size_t current = utils::GetMemoryUsage();
266         if (current <= initial) {
267             return;
268         }
269         initial = current;
270     }
271 }
272 //------------------------------------------------------------------------
273 // Common requisites of the Fibonacci tests
274 //------------------------------------------------------------------------
275 
276 const std::uintptr_t N = 20;
277 const std::uintptr_t F = 6765;
278 
279 atomic_t g_Sum;
280 
281 #define FIB_TEST_PROLOGUE() \
282     const unsigned numRepeats = g_MaxConcurrency * 4;    \
283     utils::ConcurrencyTracker::Reset()
284 
285 #define FIB_TEST_EPILOGUE(sum) \
286     CHECK(utils::ConcurrencyTracker::PeakParallelism() <= g_MaxConcurrency); \
287     CHECK( sum == numRepeats * F );
288 
289 
290 // Fibonacci tasks specified as functors
291 template<class task_group_type>
292 class FibTaskBase : utils::NoAssign, utils::NoAfterlife {
293 protected:
294     std::uintptr_t* m_pRes;
295     mutable std::uintptr_t m_Num;
296     virtual void impl() const = 0;
297 public:
298     FibTaskBase( std::uintptr_t* y, std::uintptr_t n ) : m_pRes(y), m_Num(n) {}
299     void operator()() const {
300         utils::ConcurrencyTracker ct;
301         AssertLive();
302         if( m_Num < 2 ) {
303             *m_pRes = m_Num;
304         } else {
305             impl();
306         }
307     }
308     virtual ~FibTaskBase() {}
309 };
310 
311 template<class task_group_type>
312 class FibTaskAsymmetricTreeWithFunctor : public FibTaskBase<task_group_type> {
313 public:
314     FibTaskAsymmetricTreeWithFunctor( std::uintptr_t* y, std::uintptr_t n ) : FibTaskBase<task_group_type>(y, n) {}
315     virtual void impl() const override {
316         std::uintptr_t x = ~0u;
317         task_group_type tg;
318         tg.run( FibTaskAsymmetricTreeWithFunctor(&x, this->m_Num-1) );
319         this->m_Num -= 2; tg.run_and_wait( *this );
320         *(this->m_pRes) += x;
321     }
322 };
323 
324 template<class task_group_type>
325 class FibTaskSymmetricTreeWithFunctor : public FibTaskBase<task_group_type> {
326 public:
327     FibTaskSymmetricTreeWithFunctor( std::uintptr_t* y, std::uintptr_t n ) : FibTaskBase<task_group_type>(y, n) {}
328     virtual void impl() const override {
329         std::uintptr_t x = ~0u,
330                y = ~0u;
331         task_group_type tg;
332         tg.run( FibTaskSymmetricTreeWithFunctor(&x, this->m_Num-1) );
333         tg.run( FibTaskSymmetricTreeWithFunctor(&y, this->m_Num-2) );
334         tg.wait();
335         *(this->m_pRes) = x + y;
336     }
337 };
338 
339 // Helper functions
340 template<class fib_task>
341 std::uintptr_t RunFibTask(std::uintptr_t n) {
342     std::uintptr_t res = ~0u;
343     fib_task(&res, n)();
344     return res;
345 }
346 
347 template<typename fib_task>
348 void RunFibTest() {
349     FIB_TEST_PROLOGUE();
350     std::uintptr_t sum = 0;
351     for( unsigned i = 0; i < numRepeats; ++i )
352         sum += RunFibTask<fib_task>(N);
353     FIB_TEST_EPILOGUE(sum);
354 }
355 
356 template<typename fib_task>
357 void FibFunctionNoArgs() {
358     g_Sum += RunFibTask<fib_task>(N);
359 }
360 
361 template<typename task_group_type>
362 void TestFibWithLambdas() {
363     FIB_TEST_PROLOGUE();
364     atomic_t sum;
365     sum = 0;
366     task_group_type tg;
367     for( unsigned i = 0; i < numRepeats; ++i )
368         tg.run( [&](){sum += RunFibTask<FibTaskSymmetricTreeWithFunctor<task_group_type> >(N);} );
369     tg.wait();
370     FIB_TEST_EPILOGUE(sum);
371 }
372 
373 template<typename task_group_type>
374 void TestFibWithFunctor() {
375     RunFibTest<FibTaskAsymmetricTreeWithFunctor<task_group_type> >();
376     RunFibTest< FibTaskSymmetricTreeWithFunctor<task_group_type> >();
377 }
378 
379 template<typename task_group_type>
380 void TestFibWithFunctionPtr() {
381     FIB_TEST_PROLOGUE();
382     g_Sum = 0;
383     task_group_type tg;
384     for( unsigned i = 0; i < numRepeats; ++i )
385         tg.run( &FibFunctionNoArgs<FibTaskSymmetricTreeWithFunctor<task_group_type> > );
386     tg.wait();
387     FIB_TEST_EPILOGUE(g_Sum);
388 }
389 
390 template<typename task_group_type>
391 void RunFibonacciTests() {
392     TestFibWithLambdas<task_group_type>();
393     TestFibWithFunctor<task_group_type>();
394     TestFibWithFunctionPtr<task_group_type>();
395 }
396 
397 class test_exception : public std::exception
398 {
399     const char* m_strDescription;
400 public:
401     test_exception ( const char* descr ) : m_strDescription(descr) {}
402 
403     const char* what() const throw() override { return m_strDescription; }
404 };
405 
406 #if TBB_USE_CAPTURED_EXCEPTION
407     #include "tbb/tbb_exception.h"
408     typedef tbb::captured_exception TestException;
409 #else
410     typedef test_exception TestException;
411 #endif
412 
413 #include <string.h>
414 
415 #define NUM_CHORES      512
416 #define NUM_GROUPS      64
417 #define SKIP_CHORES     (NUM_CHORES/4)
418 #define SKIP_GROUPS     (NUM_GROUPS/4)
419 #define EXCEPTION_DESCR1 "Test exception 1"
420 #define EXCEPTION_DESCR2 "Test exception 2"
421 
422 atomic_t g_ExceptionCount;
423 atomic_t g_TaskCount;
424 unsigned g_ExecutedAtCancellation;
425 bool g_Rethrow;
426 bool g_Throw;
427 
428 class ThrowingTask : utils::NoAssign, utils::NoAfterlife {
429     atomic_t &m_TaskCount;
430 public:
431     ThrowingTask( atomic_t& counter ) : m_TaskCount(counter) {}
432     void operator() () const {
433         utils::ConcurrencyTracker ct;
434         AssertLive();
435         if ( g_Throw ) {
436             if ( ++m_TaskCount == SKIP_CHORES )
437                 TBB_TEST_THROW(test_exception(EXCEPTION_DESCR1));
438             utils::yield();
439         }
440         else {
441             ++g_TaskCount;
442             while( !tbb::is_current_task_group_canceling() )
443                 utils::yield();
444         }
445     }
446 };
447 
448 inline void ResetGlobals ( bool bThrow, bool bRethrow ) {
449     g_Throw = bThrow;
450     g_Rethrow = bRethrow;
451     g_ExceptionCount = 0;
452     g_TaskCount = 0;
453     utils::ConcurrencyTracker::Reset();
454 }
455 
456 template<typename task_group_type>
457 void LaunchChildrenWithFunctor () {
458     atomic_t count;
459     count = 0;
460     task_group_type g;
461     for (unsigned i = 0; i < NUM_CHORES; ++i) {
462 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
463         if (i % 2 == 1) {
464             g.run(g.defer(ThrowingTask(count)));
465         } else
466 #endif
467         {
468             g.run(ThrowingTask(count));
469         }
470     }
471 #if TBB_USE_EXCEPTIONS
472     tbb::task_group_status status = tbb::not_complete;
473     bool exceptionCaught = false;
474     try {
475         status = g.wait();
476     } catch ( TestException& e ) {
477         CHECK_MESSAGE( e.what(), "Empty what() string" );
478         CHECK_MESSAGE( strcmp(e.what(), EXCEPTION_DESCR1) == 0, "Unknown exception" );
479         exceptionCaught = true;
480         ++g_ExceptionCount;
481     } catch( ... ) { CHECK_MESSAGE( false, "Unknown exception" ); }
482     if (g_Throw && !exceptionCaught && status != tbb::canceled) {
483         CHECK_MESSAGE(false, "No exception in the child task group");
484     }
485     if ( g_Rethrow && g_ExceptionCount > SKIP_GROUPS ) {
486         throw test_exception(EXCEPTION_DESCR2);
487     }
488 #else
489     g.wait();
490 #endif
491 }
492 
493 // Tests for cancellation and exception handling behavior
494 template<typename task_group_type>
495 void TestManualCancellationWithFunctor () {
496     ResetGlobals( false, false );
497     task_group_type tg;
498     for (unsigned i = 0; i < NUM_GROUPS; ++i) {
499         // TBB version does not require taking function address
500 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
501         if (i % 2 == 0) {
502             auto h = tg.defer(&LaunchChildrenWithFunctor<task_group_type>);
503             tg.run(std::move(h));
504         } else
505 #endif
506         {
507             tg.run(&LaunchChildrenWithFunctor<task_group_type>);
508         }
509     }
510     CHECK_MESSAGE ( !tbb::is_current_task_group_canceling(), "Unexpected cancellation" );
511     while ( g_MaxConcurrency > 1 && g_TaskCount == 0 )
512         utils::yield();
513     tg.cancel();
514     g_ExecutedAtCancellation = int(g_TaskCount);
515     tbb::task_group_status status = tg.wait();
516     CHECK_MESSAGE( status == tbb::canceled, "Task group reported invalid status." );
517     CHECK_MESSAGE( g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken" );
518     CHECK_MESSAGE( g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?" );
519     CHECK_MESSAGE( g_TaskCount <= g_ExecutedAtCancellation + utils::ConcurrencyTracker::PeakParallelism(), "Too many tasks survived cancellation" );
520 }
521 
522 #if TBB_USE_EXCEPTIONS
523 template<typename task_group_type>
524 void TestExceptionHandling1 () {
525     ResetGlobals( true, false );
526     task_group_type tg;
527     for( unsigned i = 0; i < NUM_GROUPS; ++i )
528         // TBB version does not require taking function address
529         tg.run( &LaunchChildrenWithFunctor<task_group_type> );
530     try {
531         tg.wait();
532     } catch ( ... ) {
533         CHECK_MESSAGE( false, "Unexpected exception" );
534     }
535     CHECK_MESSAGE( g_ExceptionCount <= NUM_GROUPS, "Too many exceptions from the child groups. The test is broken" );
536     CHECK_MESSAGE( g_ExceptionCount == NUM_GROUPS, "Not all child groups threw the exception" );
537 }
538 
539 template<typename task_group_type>
540 void TestExceptionHandling2 () {
541     ResetGlobals( true, true );
542     task_group_type tg;
543     bool exceptionCaught = false;
544     for( unsigned i = 0; i < NUM_GROUPS; ++i ) {
545         // TBB version does not require taking function address
546         tg.run( &LaunchChildrenWithFunctor<task_group_type> );
547     }
548     try {
549         tg.wait();
550     } catch ( TestException& e ) {
551         CHECK_MESSAGE( e.what(), "Empty what() string" );
552         CHECK_MESSAGE( strcmp(e.what(), EXCEPTION_DESCR2) == 0, "Unknown exception" );
553         exceptionCaught = true;
554     } catch( ... ) { CHECK_MESSAGE( false, "Unknown exception" ); }
555     CHECK_MESSAGE( exceptionCaught, "No exception thrown from the root task group" );
556     CHECK_MESSAGE( g_ExceptionCount >= SKIP_GROUPS, "Too few exceptions from the child groups. The test is broken" );
557     CHECK_MESSAGE( g_ExceptionCount <= NUM_GROUPS - SKIP_GROUPS, "Too many exceptions from the child groups. The test is broken" );
558     CHECK_MESSAGE( g_ExceptionCount < NUM_GROUPS - SKIP_GROUPS, "None of the child groups was cancelled" );
559 }
560 
561 template <typename task_group_type>
562 void TestExceptionHandling3() {
563     task_group_type tg;
564     try {
565         tg.run_and_wait([]() {
566             volatile bool suppress_unreachable_code_warning = true;
567             if (suppress_unreachable_code_warning) {
568                 throw 1;
569             }
570         });
571     } catch (int error) {
572         CHECK(error == 1);
573     } catch ( ... ) {
574         CHECK_MESSAGE( false, "Unexpected exception" );
575     }
576 }
577 
578 template<typename task_group_type>
579 class LaunchChildrenDriver {
580 public:
581     void Launch(task_group_type& tg) {
582         ResetGlobals(false, false);
583         for (unsigned i = 0; i < NUM_GROUPS; ++i) {
584             tg.run(LaunchChildrenWithFunctor<task_group_type>);
585         }
586         CHECK_MESSAGE(!tbb::is_current_task_group_canceling(), "Unexpected cancellation");
587         while (g_MaxConcurrency > 1 && g_TaskCount == 0)
588             utils::yield();
589     }
590 
591     void Finish() {
592         CHECK_MESSAGE(g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken");
593         CHECK_MESSAGE(g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?");
594         CHECK_MESSAGE(g_TaskCount <= g_ExecutedAtCancellation + g_MaxConcurrency, "Too many tasks survived cancellation");
595     }
596 }; // LaunchChildrenWithTaskHandleDriver
597 
598 template<typename task_group_type, bool Throw>
599 void TestMissingWait () {
600     bool exception_occurred = false,
601          unexpected_exception = false;
602     LaunchChildrenDriver<task_group_type> driver;
603     try {
604         task_group_type tg;
605         driver.Launch( tg );
606         volatile bool suppress_unreachable_code_warning = Throw;
607         if (suppress_unreachable_code_warning) {
608             throw int(); // Initiate stack unwinding
609         }
610     }
611     catch ( const tbb::missing_wait& e ) {
612         CHECK_MESSAGE( e.what(), "Error message is absent" );
613         exception_occurred = true;
614         unexpected_exception = Throw;
615     }
616     catch ( int ) {
617         exception_occurred = true;
618         unexpected_exception = !Throw;
619     }
620     catch ( ... ) {
621         exception_occurred = unexpected_exception = true;
622     }
623     CHECK( exception_occurred );
624     CHECK( !unexpected_exception );
625     driver.Finish();
626 }
627 #endif
628 
629 template<typename task_group_type>
630 void RunCancellationAndExceptionHandlingTests() {
631     TestManualCancellationWithFunctor<task_group_type>();
632 #if TBB_USE_EXCEPTIONS
633     TestExceptionHandling1<task_group_type>();
634     TestExceptionHandling2<task_group_type>();
635     TestExceptionHandling3<task_group_type>();
636     TestMissingWait<task_group_type, true>();
637     TestMissingWait<task_group_type, false>();
638 #endif
639 }
640 
641 void EmptyFunction () {}
642 
643 struct TestFunctor {
644     void operator()() { CHECK_MESSAGE( false, "Non-const operator called" ); }
645     void operator()() const { /* library requires this overload only */ }
646 };
647 
648 template<typename task_group_type>
649 void TestConstantFunctorRequirement() {
650     task_group_type g;
651     TestFunctor tf;
652     g.run( tf ); g.wait();
653     g.run_and_wait( tf );
654 }
655 
656 //------------------------------------------------------------------------
657 namespace TestMoveSemanticsNS {
658     struct TestFunctor {
659         void operator()() const {};
660     };
661 
662     struct MoveOnlyFunctor : utils::MoveOnly, TestFunctor {
663         MoveOnlyFunctor() : utils::MoveOnly() {};
664         MoveOnlyFunctor(MoveOnlyFunctor&& other) : utils::MoveOnly(std::move(other)) {};
665     };
666 
667     struct MovePreferableFunctor : utils::Movable, TestFunctor {
668         MovePreferableFunctor() : utils::Movable() {};
669         MovePreferableFunctor(MovePreferableFunctor&& other) : utils::Movable(std::move(other)) {};
670         MovePreferableFunctor(const MovePreferableFunctor& other) : utils::Movable(other) {};
671     };
672 
673     struct NoMoveNoCopyFunctor : utils::NoCopy, TestFunctor {
674         NoMoveNoCopyFunctor() : utils::NoCopy() {};
675         // mv ctor is not allowed as cp ctor from parent utils::NoCopy
676     private:
677         NoMoveNoCopyFunctor(NoMoveNoCopyFunctor&&);
678     };
679 
680      template<typename task_group_type>
681     void TestBareFunctors() {
682         task_group_type tg;
683         MovePreferableFunctor mpf;
684         // run_and_wait() doesn't have any copies or moves of arguments inside the impl
685         tg.run_and_wait( NoMoveNoCopyFunctor() );
686 
687         tg.run( MoveOnlyFunctor() );
688         tg.wait();
689 
690         tg.run( mpf );
691         tg.wait();
692         CHECK_MESSAGE(mpf.alive, "object was moved when was passed by lval");
693         mpf.Reset();
694 
695         tg.run( std::move(mpf) );
696         tg.wait();
697         CHECK_MESSAGE(!mpf.alive, "object was copied when was passed by rval");
698         mpf.Reset();
699     }
700 }
701 
702 template<typename task_group_type>
703 void TestMoveSemantics() {
704     TestMoveSemanticsNS::TestBareFunctors<task_group_type>();
705 }
706 //------------------------------------------------------------------------
707 
708 // TODO: TBB_REVAMP_TODO - enable when ETS is available
709 #if TBBTEST_USE_TBB && TBB_PREVIEW_ISOLATED_TASK_GROUP
710 namespace TestIsolationNS {
711     class DummyFunctor {
712     public:
713         DummyFunctor() {}
714         void operator()() const {
715             for ( volatile int j = 0; j < 10; ++j ) {}
716         }
717     };
718 
719     template<typename task_group_type>
720     class ParForBody {
721         task_group_type& m_tg;
722         std::atomic<bool>& m_preserved;
723         tbb::enumerable_thread_specific<int>& m_ets;
724     public:
725         ParForBody(
726             task_group_type& tg,
727             std::atomic<bool>& preserved,
728             tbb::enumerable_thread_specific<int>& ets
729         ) : m_tg(tg), m_preserved(preserved), m_ets(ets) {}
730 
731         void operator()(int) const {
732             if (++m_ets.local() > 1) m_preserved = false;
733 
734             for (int i = 0; i < 1000; ++i)
735                 m_tg.run(DummyFunctor());
736             m_tg.wait();
737             m_tg.run_and_wait(DummyFunctor());
738 
739             --m_ets.local();
740         }
741     };
742 
743     template<typename task_group_type>
744     void CheckIsolation(bool isolation_is_expected) {
745         task_group_type tg;
746         std::atomic<bool> isolation_is_preserved;
747         isolation_is_preserved = true;
748         tbb::enumerable_thread_specific<int> ets(0);
749 
750         tbb::parallel_for(0, 100, ParForBody<task_group_type>(tg, isolation_is_preserved, ets));
751 
752         ASSERT(
753             isolation_is_expected == isolation_is_preserved,
754             "Actual and expected isolation-related behaviours are different"
755         );
756     }
757 
758     // Should be called only when > 1 thread is used, because otherwise isolation is guaranteed to take place
759     void TestIsolation() {
760         CheckIsolation<tbb::task_group>(false);
761         CheckIsolation<tbb::isolated_task_group>(true);
762     }
763 }
764 #endif
765 
766 #if __TBB_USE_ADDRESS_SANITIZER
767 //! Test for thread safety for the task_group
768 //! \brief \ref error_guessing \ref resource_usage
769 TEST_CASE("Memory leaks test is not applicable under ASAN\n" * doctest::skip(true)) {}
770 #else
771 //! Test for thread safety for the task_group
772 //! \brief \ref error_guessing \ref resource_usage
773 TEST_CASE("Thread safety test for the task group") {
774     if (tbb::this_task_arena::max_concurrency() < 2) {
775         // The test requires more than one thread to check thread safety
776         return;
777     }
778     for (unsigned p=MinThread; p <= MaxThread; ++p) {
779         if (p < 2) {
780             continue;
781         }
782         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
783         g_MaxConcurrency = p;
784         TestThreadSafety<tbb::task_group>();
785     }
786 }
787 #endif
788 
789 //! Fibonacci test for task group
790 //! \brief \ref interface \ref requirement
791 TEST_CASE("Fibonacci test for the task group") {
792     for (unsigned p=MinThread; p <= MaxThread; ++p) {
793         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
794         g_MaxConcurrency = p;
795         RunFibonacciTests<tbb::task_group>();
796     }
797 }
798 
799 //! Cancellation and exception test for the task group
800 //! \brief \ref interface \ref requirement
801 TEST_CASE("Cancellation and exception test for the task group") {
802     for (unsigned p = MinThread; p <= MaxThread; ++p) {
803         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
804         tbb::task_arena a(p);
805         g_MaxConcurrency = p;
806         a.execute([] {
807             RunCancellationAndExceptionHandlingTests<tbb::task_group>();
808         });
809     }
810 }
811 
812 //! Constant functor test for the task group
813 //! \brief \ref interface \ref negative
814 TEST_CASE("Constant functor test for the task group") {
815     for (unsigned p=MinThread; p <= MaxThread; ++p) {
816         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
817         g_MaxConcurrency = p;
818         TestConstantFunctorRequirement<tbb::task_group>();
819     }
820 }
821 
822 //! Move semantics test for the task group
823 //! \brief \ref interface \ref requirement
824 TEST_CASE("Move semantics test for the task group") {
825     for (unsigned p=MinThread; p <= MaxThread; ++p) {
826         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
827         g_MaxConcurrency = p;
828         TestMoveSemantics<tbb::task_group>();
829     }
830 }
831 
832 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
833 
834 #if __TBB_USE_ADDRESS_SANITIZER
835 //! Test for thread safety for the isolated_task_group
836 //! \brief \ref error_guessing
837 TEST_CASE("Memory leaks test is not applicable under ASAN\n" * doctest::skip(true)) {}
838 #else
839 //! Test for thread safety for the isolated_task_group
840 //! \brief \ref error_guessing
841 TEST_CASE("Thread safety test for the isolated task group") {
842     if (tbb::this_task_arena::max_concurrency() < 2) {
843         // The test requires more than one thread to check thread safety
844         return;
845     }
846     for (unsigned p=MinThread; p <= MaxThread; ++p) {
847         if (p < 2) {
848             continue;
849         }
850         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
851         g_MaxConcurrency = p;
852         TestThreadSafety<tbb::isolated_task_group>();
853     }
854 }
855 #endif
856 
857 //! Cancellation and exception test for the isolated task group
858 //! \brief \ref interface \ref requirement
859 TEST_CASE("Fibonacci test for the isolated task group") {
860     for (unsigned p=MinThread; p <= MaxThread; ++p) {
861         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
862         g_MaxConcurrency = p;
863         RunFibonacciTests<tbb::isolated_task_group>();
864     }
865 }
866 
867 //! Cancellation and exception test for the isolated task group
868 //! \brief \ref interface \ref requirement
869 TEST_CASE("Cancellation and exception test for the isolated task group") {
870     for (unsigned p=MinThread; p <= MaxThread; ++p) {
871         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
872         g_MaxConcurrency = p;
873         RunCancellationAndExceptionHandlingTests<tbb::isolated_task_group>();
874     }
875 }
876 
877 //! Constant functor test for the isolated task group.
878 //! \brief \ref interface \ref negative
879 TEST_CASE("Constant functor test for the isolated task group") {
880     for (unsigned p=MinThread; p <= MaxThread; ++p) {
881         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
882         g_MaxConcurrency = p;
883         TestConstantFunctorRequirement<tbb::isolated_task_group>();
884     }
885 }
886 
887 //! Move semantics test for the isolated task group.
888 //! \brief \ref interface \ref requirement
889 TEST_CASE("Move semantics test for the isolated task group") {
890     for (unsigned p=MinThread; p <= MaxThread; ++p) {
891         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
892         g_MaxConcurrency = p;
893         TestMoveSemantics<tbb::isolated_task_group>();
894     }
895 }
896 #endif /* TBB_PREVIEW_ISOLATED_TASK_GROUP */
897 
898 void run_deep_stealing(tbb::task_group& tg1, tbb::task_group& tg2, int num_tasks, std::atomic<int>& tasks_executed) {
899     for (int i = 0; i < num_tasks; ++i) {
900         tg2.run([&tg1, &tasks_executed] {
901             volatile char consume_stack[1000]{};
902             ++tasks_executed;
903             tg1.wait();
904             utils::suppress_unused_warning(consume_stack);
905         });
906     }
907 }
908 
909 // TODO: move to the conformance test
910 //! Test for stack overflow avoidance mechanism.
911 //! \brief \ref requirement
912 TEST_CASE("Test for stack overflow avoidance mechanism") {
913     if (tbb::this_task_arena::max_concurrency() < 2) {
914         return;
915     }
916 
917     tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2);
918     tbb::task_group tg1;
919     tbb::task_group tg2;
920     std::atomic<int> tasks_executed{};
921     tg1.run_and_wait([&tg1, &tg2, &tasks_executed] {
922         run_deep_stealing(tg1, tg2, 10000, tasks_executed);
923         while (tasks_executed < 100) {
924             // Some stealing is expected to happen.
925             utils::yield();
926         }
927         CHECK(tasks_executed < 10000);
928     });
929     tg2.wait();
930     CHECK(tasks_executed == 10000);
931 }
932 
933 //! Test for stack overflow avoidance mechanism.
934 //! \brief \ref error_guessing
935 TEST_CASE("Test for stack overflow avoidance mechanism within arena") {
936     if (tbb::this_task_arena::max_concurrency() < 2) {
937         return;
938     }
939 
940     tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2);
941     tbb::task_group tg1;
942     tbb::task_group tg2;
943     std::atomic<int> tasks_executed{};
944 
945     // Determine nested task execution limit.
946     int second_thread_executed{};
947     tg1.run_and_wait([&tg1, &tg2, &tasks_executed, &second_thread_executed] {
948         run_deep_stealing(tg1, tg2, 10000, tasks_executed);
949         do {
950             second_thread_executed = tasks_executed;
951             utils::Sleep(10);
952         } while (second_thread_executed < 100 || second_thread_executed != tasks_executed);
953         CHECK(tasks_executed < 10000);
954     });
955     tg2.wait();
956     CHECK(tasks_executed == 10000);
957 
958     tasks_executed = 0;
959     tbb::task_arena a(2, 2);
960     tg1.run_and_wait([&a, &tg1, &tg2, &tasks_executed, second_thread_executed] {
961         run_deep_stealing(tg1, tg2, second_thread_executed-1, tasks_executed);
962         while (tasks_executed < second_thread_executed-1) {
963             // Wait until the second thread near the limit.
964             utils::yield();
965         }
966         tg2.run([&a, &tg1, &tasks_executed] {
967             a.execute([&tg1, &tasks_executed] {
968                 volatile char consume_stack[1000]{};
969                 ++tasks_executed;
970                 tg1.wait();
971                 utils::suppress_unused_warning(consume_stack);
972             });
973         });
974         while (tasks_executed < second_thread_executed) {
975             // Wait until the second joins the arena.
976             utils::yield();
977         }
978         a.execute([&tg1, &tg2, &tasks_executed] {
979             run_deep_stealing(tg1, tg2, 10000, tasks_executed);
980         });
981         int currently_executed{};
982         do {
983             currently_executed = tasks_executed;
984             utils::Sleep(10);
985         } while (currently_executed != tasks_executed);
986         CHECK(tasks_executed < 10000 + second_thread_executed);
987     });
988     a.execute([&tg2] {
989         tg2.wait();
990     });
991     CHECK(tasks_executed == 10000 + second_thread_executed);
992 }
993 
994 //! Test checks that we can submit work to task_group asynchronously with waiting.
995 //! \brief \ref regression
996 TEST_CASE("Async task group") {
997     int num_threads = tbb::this_task_arena::max_concurrency();
998     if (num_threads < 3) {
999         // The test requires at least 2 worker threads
1000         return;
1001     }
1002     tbb::task_arena a(2*num_threads, num_threads);
1003     utils::SpinBarrier barrier(num_threads + 2);
1004     tbb::task_group tg[2];
1005     std::atomic<bool> finished[2]{};
1006     finished[0] = false; finished[1] = false;
1007     for (int i = 0; i < 2; ++i) {
1008         a.enqueue([i, &tg, &finished, &barrier] {
1009             barrier.wait();
1010             for (int j = 0; j < 10000; ++j) {
1011                 tg[i].run([] {});
1012                 utils::yield();
1013             }
1014             finished[i] = true;
1015         });
1016     }
1017     utils::NativeParallelFor(num_threads, [&](int idx) {
1018         barrier.wait();
1019         a.execute([idx, &tg, &finished] {
1020             std::size_t counter{};
1021             while (!finished[idx%2]) {
1022                 tg[idx%2].wait();
1023                 if (counter++ % 16 == 0) utils::yield();
1024             }
1025             tg[idx%2].wait();
1026         });
1027     });
1028 }
1029 
1030 struct SelfRunner {
1031     tbb::task_group& m_tg;
1032     std::atomic<unsigned>& count;
1033     void operator()() const {
1034         unsigned previous_count = count.fetch_sub(1);
1035         if (previous_count > 1)
1036             m_tg.run( *this );
1037     }
1038 };
1039 
1040 //! Submit work to single task_group instance from inside the work
1041 //! \brief \ref error_guessing
1042 TEST_CASE("Run self using same task_group instance") {
1043     const unsigned num = 10;
1044     std::atomic<unsigned> count{num};
1045     tbb::task_group tg;
1046     SelfRunner uf{tg, count};
1047     tg.run( uf );
1048     tg.wait();
1049     CHECK_MESSAGE(
1050         count == 0,
1051         "Not all tasks were spawned from inside the functor running within task_group."
1052     );
1053 }
1054 
1055 #if TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1056 
1057 namespace accept_task_group_context {
1058 
1059 template <typename TaskGroup, typename CancelF, typename WaitF>
1060 void run_cancellation_use_case(CancelF&& cancel, WaitF&& wait) {
1061     std::atomic<bool> outer_cancelled{false};
1062     std::atomic<unsigned> count{13};
1063 
1064     tbb::task_group_context inner_ctx(tbb::task_group_context::isolated);
1065     TaskGroup inner_tg(inner_ctx);
1066 
1067     tbb::task_group outer_tg;
1068     auto outer_tg_task = [&] {
1069         inner_tg.run([&] {
1070             utils::SpinWaitUntilEq(outer_cancelled, true);
1071             inner_tg.run( SelfRunner{inner_tg, count} );
1072         });
1073 
1074         utils::try_call([&] {
1075             std::forward<CancelF>(cancel)(outer_tg);
1076         }).on_completion([&] {
1077             outer_cancelled = true;
1078         });
1079     };
1080 
1081     auto check = [&] {
1082         tbb::task_group_status outer_status = tbb::task_group_status::not_complete;
1083         outer_status = std::forward<WaitF>(wait)(outer_tg);
1084         CHECK_MESSAGE(
1085             outer_status == tbb::task_group_status::canceled,
1086             "Outer task group should have been cancelled."
1087         );
1088 
1089         tbb::task_group_status inner_status = inner_tg.wait();
1090         CHECK_MESSAGE(
1091             inner_status == tbb::task_group_status::complete,
1092             "Inner task group should have completed despite the cancellation of the outer one."
1093         );
1094 
1095         CHECK_MESSAGE(0 == count, "Some of the inner group tasks were not executed.");
1096     };
1097 
1098     outer_tg.run(outer_tg_task);
1099     check();
1100 }
1101 
1102 template <typename TaskGroup>
1103 void test() {
1104     run_cancellation_use_case<TaskGroup>(
1105         [](tbb::task_group& outer) { outer.cancel(); },
1106         [](tbb::task_group& outer) { return outer.wait(); }
1107     );
1108 
1109 #if TBB_USE_EXCEPTIONS
1110     run_cancellation_use_case<TaskGroup>(
1111         [](tbb::task_group& /*outer*/) {
1112             volatile bool suppress_unreachable_code_warning = true;
1113             if (suppress_unreachable_code_warning) {
1114                 throw int();
1115             }
1116         },
1117         [](tbb::task_group& outer) {
1118             try {
1119                 outer.wait();
1120                 return tbb::task_group_status::complete;
1121             } catch(const int&) {
1122                 return tbb::task_group_status::canceled;
1123             }
1124         }
1125     );
1126 #endif
1127 }
1128 
1129 } // namespace accept_task_group_context
1130 
1131 //! Respect task_group_context passed from outside
1132 //! \brief \ref interface \ref requirement
1133 TEST_CASE("Respect task_group_context passed from outside") {
1134     accept_task_group_context::test<tbb::task_group>();
1135 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
1136     accept_task_group_context::test<tbb::isolated_task_group>();
1137 #endif
1138 }
1139 #endif // TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1140 
1141 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1142 //! Test checks that for lost task handle
1143 //! \brief \ref requirement
1144 TEST_CASE("Task handle created but not run"){
1145     {
1146         tbb::task_group tg;
1147 
1148         std::atomic<bool> run {false};
1149 
1150         auto h = tg.defer([&]{
1151             run = true;
1152         });
1153         CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
1154     }
1155 }
1156 
1157 //! Basic test for task handle
1158 //! \brief \ref interface \ref requirement
1159 TEST_CASE("Task handle run"){
1160     tbb::task_handle h;
1161 
1162     tbb::task_group tg;
1163     std::atomic<bool> run {false};
1164 
1165     h = tg.defer([&]{
1166         run = true;
1167     });
1168     CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
1169     tg.run(std::move(h));
1170     tg.wait();
1171     CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
1172 
1173     CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
1174 }
1175 
1176 //! Basic test for task handle
1177 //! \brief \ref interface \ref requirement
1178 TEST_CASE("Task handle run_and_wait"){
1179     tbb::task_handle h;
1180 
1181     tbb::task_group tg;
1182     bool run {false};
1183 
1184     h = tg.defer([&]{
1185         run = true;
1186     });
1187     CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
1188     tg.run_and_wait(std::move(h));
1189     CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
1190 
1191     CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
1192 }
1193 //! Test for empty check
1194 //! \brief \ref interface
1195 TEST_CASE("Task handle empty check"){
1196     tbb::task_group tg;
1197 
1198     tbb::task_handle h;
1199 
1200     bool empty = (h == nullptr);
1201     CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
1202 
1203     h = tg.defer([]{});
1204 
1205     CHECK_MESSAGE(h != nullptr, "delayed task returned by task_group::delayed should not be empty");
1206 }
1207 
1208 //! Test for comparison operations
1209 //! \brief \ref interface
1210 TEST_CASE("Task handle comparison/empty checks"){
1211     tbb::task_group tg;
1212 
1213     tbb::task_handle h;
1214 
1215     bool empty =  ! static_cast<bool>(h);
1216     CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
1217     CHECK_MESSAGE(h == nullptr, "default constructed task_handle should be empty");
1218     CHECK_MESSAGE(nullptr == h, "default constructed task_handle should be empty");
1219 
1220     h = tg.defer([]{});
1221 
1222     CHECK_MESSAGE(h != nullptr, "deferred task returned by task_group::defer() should not be empty");
1223     CHECK_MESSAGE(nullptr != h, "deferred task returned by task_group::defer() should not be empty");
1224 
1225 }
1226 
1227 //! Test that task_handle prolongs task_group::wait
1228 //! \brief \ref requirement
1229 TEST_CASE("Task handle blocks wait"){
1230     tbb::task_group tg;
1231 
1232     std::atomic<bool> completed  {false};
1233     std::atomic<bool> start_wait {false};
1234     std::atomic<bool> thread_started{false};
1235 
1236     tbb::task_handle h = tg.defer([&]{
1237         completed = true;
1238     });
1239 
1240     std::thread wait_thread {[&]{
1241         CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
1242 
1243         thread_started = true;
1244         utils::SpinWaitUntilEq(start_wait, true);
1245         tg.wait();
1246         CHECK_MESSAGE(completed == true, "Deferred task should be completed when task_group::wait exits");
1247     }};
1248 
1249     utils::SpinWaitUntilEq(thread_started, true);
1250     CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
1251 
1252     tg.run(std::move(h));
1253     //TODO: more accurate test (with fixed number of threads (1 ?) to guarantee correctness of following assert)
1254     //CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) and wait is called");
1255     start_wait = true;
1256     wait_thread.join();
1257 }
1258 
1259 //! The test for task_handle inside other task waiting with run
1260 //! \brief \ref requirement
1261 TEST_CASE("Task handle for scheduler bypass"){
1262     tbb::task_group tg;
1263     std::atomic<bool> run {false};
1264 
1265     tg.run([&]{
1266         return tg.defer([&]{
1267             run = true;
1268         });
1269     });
1270 
1271     tg.wait();
1272     CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run");
1273 }
1274 
1275 //! The test for task_handle inside other task waiting with run_and_wait
1276 //! \brief \ref requirement
1277 TEST_CASE("Task handle for scheduler bypass via run_and_wait"){
1278     tbb::task_group tg;
1279     std::atomic<bool> run {false};
1280 
1281     tg.run_and_wait([&]{
1282         return tg.defer([&]{
1283             run = true;
1284         });
1285     });
1286 
1287     CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run");
1288 }
1289 
1290 #if TBB_USE_EXCEPTIONS
1291 //! The test for exception handling in task_handle
1292 //! \brief \ref requirement
1293 TEST_CASE("Task handle exception propagation"){
1294     tbb::task_group tg;
1295 
1296     tbb::task_handle h = tg.defer([&]{
1297         volatile bool suppress_unreachable_code_warning = true;
1298         if (suppress_unreachable_code_warning) {
1299             throw std::runtime_error{ "" };
1300         }
1301     });
1302 
1303     tg.run(std::move(h));
1304 
1305     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
1306 }
1307 
1308 //! The test for error in scheduling empty task_handle
1309 //! \brief \ref requirement
1310 TEST_CASE("Empty task_handle cannot be scheduled"){
1311     tbb::task_group tg;
1312 
1313     CHECK_THROWS_WITH_AS(tg.run(tbb::task_handle{}), "Attempt to schedule empty task_handle", std::runtime_error);
1314 }
1315 
1316 //! The test for error in task_handle being scheduled into task_group different from one it was created from
1317 //! \brief \ref requirement
1318 TEST_CASE("task_handle cannot be scheduled into different task_group"){
1319     tbb::task_group tg;
1320     tbb::task_group tg1;
1321 
1322     CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error);
1323 }
1324 
1325 //! The test for error in task_handle being scheduled into task_group different from one it was created from
1326 //! \brief \ref requirement
1327 TEST_CASE("task_handle cannot be scheduled into other task_group of the same context"
1328         * doctest::should_fail()    //Implementation is no there yet, as it is not clear that is the expected behavior
1329         * doctest::skip()           //skip the test for now, to not pollute the test log
1330 )
1331 {
1332     tbb::task_group_context ctx;
1333 
1334     tbb::task_group tg(ctx);
1335     tbb::task_group tg1(ctx);
1336 
1337     CHECK_NOTHROW(tg.run(tg.defer([]{})));
1338     CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error);
1339 }
1340 
1341 #endif // TBB_USE_EXCEPTIONS
1342 #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1343