xref: /oneTBB/test/tbb/test_task_group.cpp (revision adf44fbf)
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 using TestException = test_exception;
407 
408 #include <string.h>
409 
410 #define NUM_CHORES      512
411 #define NUM_GROUPS      64
412 #define SKIP_CHORES     (NUM_CHORES/4)
413 #define SKIP_GROUPS     (NUM_GROUPS/4)
414 #define EXCEPTION_DESCR1 "Test exception 1"
415 #define EXCEPTION_DESCR2 "Test exception 2"
416 
417 atomic_t g_ExceptionCount;
418 atomic_t g_TaskCount;
419 unsigned g_ExecutedAtCancellation;
420 bool g_Rethrow;
421 bool g_Throw;
422 
423 class ThrowingTask : utils::NoAssign, utils::NoAfterlife {
424     atomic_t &m_TaskCount;
425 public:
426     ThrowingTask( atomic_t& counter ) : m_TaskCount(counter) {}
427     void operator() () const {
428         utils::ConcurrencyTracker ct;
429         AssertLive();
430         if ( g_Throw ) {
431             if ( ++m_TaskCount == SKIP_CHORES )
432                 TBB_TEST_THROW(test_exception(EXCEPTION_DESCR1));
433             utils::yield();
434         }
435         else {
436             ++g_TaskCount;
437             while( !tbb::is_current_task_group_canceling() )
438                 utils::yield();
439         }
440     }
441 };
442 
443 inline void ResetGlobals ( bool bThrow, bool bRethrow ) {
444     g_Throw = bThrow;
445     g_Rethrow = bRethrow;
446     g_ExceptionCount = 0;
447     g_TaskCount = 0;
448     utils::ConcurrencyTracker::Reset();
449 }
450 
451 template<typename task_group_type>
452 void LaunchChildrenWithFunctor () {
453     atomic_t count;
454     count = 0;
455     task_group_type g;
456     for (unsigned i = 0; i < NUM_CHORES; ++i) {
457 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
458         if (i % 2 == 1) {
459             g.run(g.defer(ThrowingTask(count)));
460         } else
461 #endif
462         {
463             g.run(ThrowingTask(count));
464         }
465     }
466 #if TBB_USE_EXCEPTIONS
467     tbb::task_group_status status = tbb::not_complete;
468     bool exceptionCaught = false;
469     try {
470         status = g.wait();
471     } catch ( TestException& e ) {
472         CHECK_MESSAGE( e.what(), "Empty what() string" );
473         CHECK_MESSAGE( strcmp(e.what(), EXCEPTION_DESCR1) == 0, "Unknown exception" );
474         exceptionCaught = true;
475         ++g_ExceptionCount;
476     } catch( ... ) { CHECK_MESSAGE( false, "Unknown exception" ); }
477     if (g_Throw && !exceptionCaught && status != tbb::canceled) {
478         CHECK_MESSAGE(false, "No exception in the child task group");
479     }
480     if ( g_Rethrow && g_ExceptionCount > SKIP_GROUPS ) {
481         throw test_exception(EXCEPTION_DESCR2);
482     }
483 #else
484     g.wait();
485 #endif
486 }
487 
488 // Tests for cancellation and exception handling behavior
489 template<typename task_group_type>
490 void TestManualCancellationWithFunctor () {
491     ResetGlobals( false, false );
492     task_group_type tg;
493     for (unsigned i = 0; i < NUM_GROUPS; ++i) {
494         // TBB version does not require taking function address
495 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
496         if (i % 2 == 0) {
497             auto h = tg.defer(&LaunchChildrenWithFunctor<task_group_type>);
498             tg.run(std::move(h));
499         } else
500 #endif
501         {
502             tg.run(&LaunchChildrenWithFunctor<task_group_type>);
503         }
504     }
505     CHECK_MESSAGE ( !tbb::is_current_task_group_canceling(), "Unexpected cancellation" );
506     while ( g_MaxConcurrency > 1 && g_TaskCount == 0 )
507         utils::yield();
508     tg.cancel();
509     g_ExecutedAtCancellation = int(g_TaskCount);
510     tbb::task_group_status status = tg.wait();
511     CHECK_MESSAGE( status == tbb::canceled, "Task group reported invalid status." );
512     CHECK_MESSAGE( g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken" );
513     CHECK_MESSAGE( g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?" );
514     CHECK_MESSAGE( g_TaskCount <= g_ExecutedAtCancellation + utils::ConcurrencyTracker::PeakParallelism(), "Too many tasks survived cancellation" );
515 }
516 
517 #if TBB_USE_EXCEPTIONS
518 template<typename task_group_type>
519 void TestExceptionHandling1 () {
520     ResetGlobals( true, false );
521     task_group_type tg;
522     for( unsigned i = 0; i < NUM_GROUPS; ++i )
523         // TBB version does not require taking function address
524         tg.run( &LaunchChildrenWithFunctor<task_group_type> );
525     try {
526         tg.wait();
527     } catch ( ... ) {
528         CHECK_MESSAGE( false, "Unexpected exception" );
529     }
530     CHECK_MESSAGE( g_ExceptionCount <= NUM_GROUPS, "Too many exceptions from the child groups. The test is broken" );
531     CHECK_MESSAGE( g_ExceptionCount == NUM_GROUPS, "Not all child groups threw the exception" );
532 }
533 
534 template<typename task_group_type>
535 void TestExceptionHandling2 () {
536     ResetGlobals( true, true );
537     task_group_type tg;
538     bool exceptionCaught = false;
539     for( unsigned i = 0; i < NUM_GROUPS; ++i ) {
540         // TBB version does not require taking function address
541         tg.run( &LaunchChildrenWithFunctor<task_group_type> );
542     }
543     try {
544         tg.wait();
545     } catch ( TestException& e ) {
546         CHECK_MESSAGE( e.what(), "Empty what() string" );
547         CHECK_MESSAGE( strcmp(e.what(), EXCEPTION_DESCR2) == 0, "Unknown exception" );
548         exceptionCaught = true;
549     } catch( ... ) { CHECK_MESSAGE( false, "Unknown exception" ); }
550     CHECK_MESSAGE( exceptionCaught, "No exception thrown from the root task group" );
551     CHECK_MESSAGE( g_ExceptionCount >= SKIP_GROUPS, "Too few exceptions from the child groups. The test is broken" );
552     CHECK_MESSAGE( g_ExceptionCount <= NUM_GROUPS - SKIP_GROUPS, "Too many exceptions from the child groups. The test is broken" );
553     CHECK_MESSAGE( g_ExceptionCount < NUM_GROUPS - SKIP_GROUPS, "None of the child groups was cancelled" );
554 }
555 
556 template <typename task_group_type>
557 void TestExceptionHandling3() {
558     task_group_type tg;
559     try {
560         tg.run_and_wait([]() {
561             volatile bool suppress_unreachable_code_warning = true;
562             if (suppress_unreachable_code_warning) {
563                 throw 1;
564             }
565         });
566     } catch (int error) {
567         CHECK(error == 1);
568     } catch ( ... ) {
569         CHECK_MESSAGE( false, "Unexpected exception" );
570     }
571 }
572 
573 template<typename task_group_type>
574 class LaunchChildrenDriver {
575 public:
576     void Launch(task_group_type& tg) {
577         ResetGlobals(false, false);
578         for (unsigned i = 0; i < NUM_GROUPS; ++i) {
579             tg.run(LaunchChildrenWithFunctor<task_group_type>);
580         }
581         CHECK_MESSAGE(!tbb::is_current_task_group_canceling(), "Unexpected cancellation");
582         while (g_MaxConcurrency > 1 && g_TaskCount == 0)
583             utils::yield();
584     }
585 
586     void Finish() {
587         CHECK_MESSAGE(g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken");
588         CHECK_MESSAGE(g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?");
589         CHECK_MESSAGE(g_TaskCount <= g_ExecutedAtCancellation + g_MaxConcurrency, "Too many tasks survived cancellation");
590     }
591 }; // LaunchChildrenWithTaskHandleDriver
592 
593 template<typename task_group_type, bool Throw>
594 void TestMissingWait () {
595     bool exception_occurred = false,
596          unexpected_exception = false;
597     LaunchChildrenDriver<task_group_type> driver;
598     try {
599         task_group_type tg;
600         driver.Launch( tg );
601         volatile bool suppress_unreachable_code_warning = Throw;
602         if (suppress_unreachable_code_warning) {
603             throw int(); // Initiate stack unwinding
604         }
605     }
606     catch ( const tbb::missing_wait& e ) {
607         CHECK_MESSAGE( e.what(), "Error message is absent" );
608         exception_occurred = true;
609         unexpected_exception = Throw;
610     }
611     catch ( int ) {
612         exception_occurred = true;
613         unexpected_exception = !Throw;
614     }
615     catch ( ... ) {
616         exception_occurred = unexpected_exception = true;
617     }
618     CHECK( exception_occurred );
619     CHECK( !unexpected_exception );
620     driver.Finish();
621 }
622 #endif
623 
624 template<typename task_group_type>
625 void RunCancellationAndExceptionHandlingTests() {
626     TestManualCancellationWithFunctor<task_group_type>();
627 #if TBB_USE_EXCEPTIONS
628     TestExceptionHandling1<task_group_type>();
629     TestExceptionHandling2<task_group_type>();
630     TestExceptionHandling3<task_group_type>();
631     TestMissingWait<task_group_type, true>();
632     TestMissingWait<task_group_type, false>();
633 #endif
634 }
635 
636 void EmptyFunction () {}
637 
638 struct TestFunctor {
639     void operator()() { CHECK_MESSAGE( false, "Non-const operator called" ); }
640     void operator()() const { /* library requires this overload only */ }
641 };
642 
643 template<typename task_group_type>
644 void TestConstantFunctorRequirement() {
645     task_group_type g;
646     TestFunctor tf;
647     g.run( tf ); g.wait();
648     g.run_and_wait( tf );
649 }
650 
651 //------------------------------------------------------------------------
652 namespace TestMoveSemanticsNS {
653     struct TestFunctor {
654         void operator()() const {};
655     };
656 
657     struct MoveOnlyFunctor : utils::MoveOnly, TestFunctor {
658         MoveOnlyFunctor() : utils::MoveOnly() {};
659         MoveOnlyFunctor(MoveOnlyFunctor&& other) : utils::MoveOnly(std::move(other)) {};
660     };
661 
662     struct MovePreferableFunctor : utils::Movable, TestFunctor {
663         MovePreferableFunctor() : utils::Movable() {};
664         MovePreferableFunctor(MovePreferableFunctor&& other) : utils::Movable(std::move(other)) {};
665         MovePreferableFunctor(const MovePreferableFunctor& other) : utils::Movable(other) {};
666     };
667 
668     struct NoMoveNoCopyFunctor : utils::NoCopy, TestFunctor {
669         NoMoveNoCopyFunctor() : utils::NoCopy() {};
670         // mv ctor is not allowed as cp ctor from parent utils::NoCopy
671     private:
672         NoMoveNoCopyFunctor(NoMoveNoCopyFunctor&&);
673     };
674 
675      template<typename task_group_type>
676     void TestBareFunctors() {
677         task_group_type tg;
678         MovePreferableFunctor mpf;
679         // run_and_wait() doesn't have any copies or moves of arguments inside the impl
680         tg.run_and_wait( NoMoveNoCopyFunctor() );
681 
682         tg.run( MoveOnlyFunctor() );
683         tg.wait();
684 
685         tg.run( mpf );
686         tg.wait();
687         CHECK_MESSAGE(mpf.alive, "object was moved when was passed by lval");
688         mpf.Reset();
689 
690         tg.run( std::move(mpf) );
691         tg.wait();
692         CHECK_MESSAGE(!mpf.alive, "object was copied when was passed by rval");
693         mpf.Reset();
694     }
695 }
696 
697 template<typename task_group_type>
698 void TestMoveSemantics() {
699     TestMoveSemanticsNS::TestBareFunctors<task_group_type>();
700 }
701 //------------------------------------------------------------------------
702 
703 // TODO: TBB_REVAMP_TODO - enable when ETS is available
704 #if TBBTEST_USE_TBB && TBB_PREVIEW_ISOLATED_TASK_GROUP
705 namespace TestIsolationNS {
706     class DummyFunctor {
707     public:
708         DummyFunctor() {}
709         void operator()() const {
710             for ( volatile int j = 0; j < 10; ++j ) {}
711         }
712     };
713 
714     template<typename task_group_type>
715     class ParForBody {
716         task_group_type& m_tg;
717         std::atomic<bool>& m_preserved;
718         tbb::enumerable_thread_specific<int>& m_ets;
719     public:
720         ParForBody(
721             task_group_type& tg,
722             std::atomic<bool>& preserved,
723             tbb::enumerable_thread_specific<int>& ets
724         ) : m_tg(tg), m_preserved(preserved), m_ets(ets) {}
725 
726         void operator()(int) const {
727             if (++m_ets.local() > 1) m_preserved = false;
728 
729             for (int i = 0; i < 1000; ++i)
730                 m_tg.run(DummyFunctor());
731             m_tg.wait();
732             m_tg.run_and_wait(DummyFunctor());
733 
734             --m_ets.local();
735         }
736     };
737 
738     template<typename task_group_type>
739     void CheckIsolation(bool isolation_is_expected) {
740         task_group_type tg;
741         std::atomic<bool> isolation_is_preserved;
742         isolation_is_preserved = true;
743         tbb::enumerable_thread_specific<int> ets(0);
744 
745         tbb::parallel_for(0, 100, ParForBody<task_group_type>(tg, isolation_is_preserved, ets));
746 
747         ASSERT(
748             isolation_is_expected == isolation_is_preserved,
749             "Actual and expected isolation-related behaviours are different"
750         );
751     }
752 
753     // Should be called only when > 1 thread is used, because otherwise isolation is guaranteed to take place
754     void TestIsolation() {
755         CheckIsolation<tbb::task_group>(false);
756         CheckIsolation<tbb::isolated_task_group>(true);
757     }
758 }
759 #endif
760 
761 #if __TBB_USE_ADDRESS_SANITIZER
762 //! Test for thread safety for the task_group
763 //! \brief \ref error_guessing \ref resource_usage
764 TEST_CASE("Memory leaks test is not applicable under ASAN\n" * doctest::skip(true)) {}
765 #else
766 //! Test for thread safety for the task_group
767 //! \brief \ref error_guessing \ref resource_usage
768 TEST_CASE("Thread safety test for the task group") {
769     if (tbb::this_task_arena::max_concurrency() < 2) {
770         // The test requires more than one thread to check thread safety
771         return;
772     }
773     for (unsigned p=MinThread; p <= MaxThread; ++p) {
774         if (p < 2) {
775             continue;
776         }
777         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
778         g_MaxConcurrency = p;
779         TestThreadSafety<tbb::task_group>();
780     }
781 }
782 #endif
783 
784 //! Fibonacci test for task group
785 //! \brief \ref interface \ref requirement
786 TEST_CASE("Fibonacci test for the task group") {
787     for (unsigned p=MinThread; p <= MaxThread; ++p) {
788         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
789         g_MaxConcurrency = p;
790         RunFibonacciTests<tbb::task_group>();
791     }
792 }
793 
794 //! Cancellation and exception test for the task group
795 //! \brief \ref interface \ref requirement
796 TEST_CASE("Cancellation and exception test for the task group") {
797     for (unsigned p = MinThread; p <= MaxThread; ++p) {
798         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
799         tbb::task_arena a(p);
800         g_MaxConcurrency = p;
801         a.execute([] {
802             RunCancellationAndExceptionHandlingTests<tbb::task_group>();
803         });
804     }
805 }
806 
807 //! Constant functor test for the task group
808 //! \brief \ref interface \ref negative
809 TEST_CASE("Constant functor test for the task group") {
810     for (unsigned p=MinThread; p <= MaxThread; ++p) {
811         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
812         g_MaxConcurrency = p;
813         TestConstantFunctorRequirement<tbb::task_group>();
814     }
815 }
816 
817 //! Move semantics test for the task group
818 //! \brief \ref interface \ref requirement
819 TEST_CASE("Move semantics test for the task group") {
820     for (unsigned p=MinThread; p <= MaxThread; ++p) {
821         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
822         g_MaxConcurrency = p;
823         TestMoveSemantics<tbb::task_group>();
824     }
825 }
826 
827 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
828 
829 #if __TBB_USE_ADDRESS_SANITIZER
830 //! Test for thread safety for the isolated_task_group
831 //! \brief \ref error_guessing
832 TEST_CASE("Memory leaks test is not applicable under ASAN\n" * doctest::skip(true)) {}
833 #else
834 //! Test for thread safety for the isolated_task_group
835 //! \brief \ref error_guessing
836 TEST_CASE("Thread safety test for the isolated task group") {
837     if (tbb::this_task_arena::max_concurrency() < 2) {
838         // The test requires more than one thread to check thread safety
839         return;
840     }
841     for (unsigned p=MinThread; p <= MaxThread; ++p) {
842         if (p < 2) {
843             continue;
844         }
845         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
846         g_MaxConcurrency = p;
847         TestThreadSafety<tbb::isolated_task_group>();
848     }
849 }
850 #endif
851 
852 //! Cancellation and exception test for the isolated task group
853 //! \brief \ref interface \ref requirement
854 TEST_CASE("Fibonacci test for the isolated task group") {
855     for (unsigned p=MinThread; p <= MaxThread; ++p) {
856         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
857         g_MaxConcurrency = p;
858         RunFibonacciTests<tbb::isolated_task_group>();
859     }
860 }
861 
862 //! Cancellation and exception test for the isolated task group
863 //! \brief \ref interface \ref requirement
864 TEST_CASE("Cancellation and exception test for the isolated task group") {
865     for (unsigned p=MinThread; p <= MaxThread; ++p) {
866         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
867         g_MaxConcurrency = p;
868         RunCancellationAndExceptionHandlingTests<tbb::isolated_task_group>();
869     }
870 }
871 
872 //! Constant functor test for the isolated task group.
873 //! \brief \ref interface \ref negative
874 TEST_CASE("Constant functor test for the isolated task group") {
875     for (unsigned p=MinThread; p <= MaxThread; ++p) {
876         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
877         g_MaxConcurrency = p;
878         TestConstantFunctorRequirement<tbb::isolated_task_group>();
879     }
880 }
881 
882 //! Move semantics test for the isolated task group.
883 //! \brief \ref interface \ref requirement
884 TEST_CASE("Move semantics test for the isolated task group") {
885     for (unsigned p=MinThread; p <= MaxThread; ++p) {
886         tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p);
887         g_MaxConcurrency = p;
888         TestMoveSemantics<tbb::isolated_task_group>();
889     }
890 }
891 #endif /* TBB_PREVIEW_ISOLATED_TASK_GROUP */
892 
893 void run_deep_stealing(tbb::task_group& tg1, tbb::task_group& tg2, int num_tasks, std::atomic<int>& tasks_executed) {
894     for (int i = 0; i < num_tasks; ++i) {
895         tg2.run([&tg1, &tasks_executed] {
896             volatile char consume_stack[1000]{};
897             ++tasks_executed;
898             tg1.wait();
899             utils::suppress_unused_warning(consume_stack);
900         });
901     }
902 }
903 
904 // TODO: move to the conformance test
905 //! Test for stack overflow avoidance mechanism.
906 //! \brief \ref requirement
907 TEST_CASE("Test for stack overflow avoidance mechanism") {
908     if (tbb::this_task_arena::max_concurrency() < 2) {
909         return;
910     }
911 
912     tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2);
913     tbb::task_group tg1;
914     tbb::task_group tg2;
915     std::atomic<int> tasks_executed{};
916     tg1.run_and_wait([&tg1, &tg2, &tasks_executed] {
917         run_deep_stealing(tg1, tg2, 10000, tasks_executed);
918         while (tasks_executed < 100) {
919             // Some stealing is expected to happen.
920             utils::yield();
921         }
922         CHECK(tasks_executed < 10000);
923     });
924     tg2.wait();
925     CHECK(tasks_executed == 10000);
926 }
927 
928 //! Test for stack overflow avoidance mechanism.
929 //! \brief \ref error_guessing
930 TEST_CASE("Test for stack overflow avoidance mechanism within arena") {
931     if (tbb::this_task_arena::max_concurrency() < 2) {
932         return;
933     }
934 
935     tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2);
936     tbb::task_group tg1;
937     tbb::task_group tg2;
938     std::atomic<int> tasks_executed{};
939 
940     // Determine nested task execution limit.
941     int second_thread_executed{};
942     tg1.run_and_wait([&tg1, &tg2, &tasks_executed, &second_thread_executed] {
943         run_deep_stealing(tg1, tg2, 10000, tasks_executed);
944         do {
945             second_thread_executed = tasks_executed;
946             utils::Sleep(10);
947         } while (second_thread_executed < 100 || second_thread_executed != tasks_executed);
948         CHECK(tasks_executed < 10000);
949     });
950     tg2.wait();
951     CHECK(tasks_executed == 10000);
952 
953     tasks_executed = 0;
954     tbb::task_arena a(2, 2);
955     tg1.run_and_wait([&a, &tg1, &tg2, &tasks_executed, second_thread_executed] {
956         run_deep_stealing(tg1, tg2, second_thread_executed-1, tasks_executed);
957         while (tasks_executed < second_thread_executed-1) {
958             // Wait until the second thread near the limit.
959             utils::yield();
960         }
961         tg2.run([&a, &tg1, &tasks_executed] {
962             a.execute([&tg1, &tasks_executed] {
963                 volatile char consume_stack[1000]{};
964                 ++tasks_executed;
965                 tg1.wait();
966                 utils::suppress_unused_warning(consume_stack);
967             });
968         });
969         while (tasks_executed < second_thread_executed) {
970             // Wait until the second joins the arena.
971             utils::yield();
972         }
973         a.execute([&tg1, &tg2, &tasks_executed] {
974             run_deep_stealing(tg1, tg2, 10000, tasks_executed);
975         });
976         int currently_executed{};
977         do {
978             currently_executed = tasks_executed;
979             utils::Sleep(10);
980         } while (currently_executed != tasks_executed);
981         CHECK(tasks_executed < 10000 + second_thread_executed);
982     });
983     a.execute([&tg2] {
984         tg2.wait();
985     });
986     CHECK(tasks_executed == 10000 + second_thread_executed);
987 }
988 
989 //! Test checks that we can submit work to task_group asynchronously with waiting.
990 //! \brief \ref regression
991 TEST_CASE("Async task group") {
992     int num_threads = tbb::this_task_arena::max_concurrency();
993     if (num_threads < 3) {
994         // The test requires at least 2 worker threads
995         return;
996     }
997     tbb::task_arena a(2*num_threads, num_threads);
998     utils::SpinBarrier barrier(num_threads + 2);
999     tbb::task_group tg[2];
1000     std::atomic<bool> finished[2]{};
1001     finished[0] = false; finished[1] = false;
1002     for (int i = 0; i < 2; ++i) {
1003         a.enqueue([i, &tg, &finished, &barrier] {
1004             barrier.wait();
1005             for (int j = 0; j < 10000; ++j) {
1006                 tg[i].run([] {});
1007                 utils::yield();
1008             }
1009             finished[i] = true;
1010         });
1011     }
1012     utils::NativeParallelFor(num_threads, [&](int idx) {
1013         barrier.wait();
1014         a.execute([idx, &tg, &finished] {
1015             std::size_t counter{};
1016             while (!finished[idx%2]) {
1017                 tg[idx%2].wait();
1018                 if (counter++ % 16 == 0) utils::yield();
1019             }
1020             tg[idx%2].wait();
1021         });
1022     });
1023 }
1024 
1025 struct SelfRunner {
1026     tbb::task_group& m_tg;
1027     std::atomic<unsigned>& count;
1028     void operator()() const {
1029         unsigned previous_count = count.fetch_sub(1);
1030         if (previous_count > 1)
1031             m_tg.run( *this );
1032     }
1033 };
1034 
1035 //! Submit work to single task_group instance from inside the work
1036 //! \brief \ref error_guessing
1037 TEST_CASE("Run self using same task_group instance") {
1038     const unsigned num = 10;
1039     std::atomic<unsigned> count{num};
1040     tbb::task_group tg;
1041     SelfRunner uf{tg, count};
1042     tg.run( uf );
1043     tg.wait();
1044     CHECK_MESSAGE(
1045         count == 0,
1046         "Not all tasks were spawned from inside the functor running within task_group."
1047     );
1048 }
1049 
1050 #if TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1051 
1052 namespace accept_task_group_context {
1053 
1054 template <typename TaskGroup, typename CancelF, typename WaitF>
1055 void run_cancellation_use_case(CancelF&& cancel, WaitF&& wait) {
1056     std::atomic<bool> outer_cancelled{false};
1057     std::atomic<unsigned> count{13};
1058 
1059     tbb::task_group_context inner_ctx(tbb::task_group_context::isolated);
1060     TaskGroup inner_tg(inner_ctx);
1061 
1062     tbb::task_group outer_tg;
1063     auto outer_tg_task = [&] {
1064         inner_tg.run([&] {
1065             utils::SpinWaitUntilEq(outer_cancelled, true);
1066             inner_tg.run( SelfRunner{inner_tg, count} );
1067         });
1068 
1069         utils::try_call([&] {
1070             std::forward<CancelF>(cancel)(outer_tg);
1071         }).on_completion([&] {
1072             outer_cancelled = true;
1073         });
1074     };
1075 
1076     auto check = [&] {
1077         tbb::task_group_status outer_status = tbb::task_group_status::not_complete;
1078         outer_status = std::forward<WaitF>(wait)(outer_tg);
1079         CHECK_MESSAGE(
1080             outer_status == tbb::task_group_status::canceled,
1081             "Outer task group should have been cancelled."
1082         );
1083 
1084         tbb::task_group_status inner_status = inner_tg.wait();
1085         CHECK_MESSAGE(
1086             inner_status == tbb::task_group_status::complete,
1087             "Inner task group should have completed despite the cancellation of the outer one."
1088         );
1089 
1090         CHECK_MESSAGE(0 == count, "Some of the inner group tasks were not executed.");
1091     };
1092 
1093     outer_tg.run(outer_tg_task);
1094     check();
1095 }
1096 
1097 template <typename TaskGroup>
1098 void test() {
1099     run_cancellation_use_case<TaskGroup>(
1100         [](tbb::task_group& outer) { outer.cancel(); },
1101         [](tbb::task_group& outer) { return outer.wait(); }
1102     );
1103 
1104 #if TBB_USE_EXCEPTIONS
1105     run_cancellation_use_case<TaskGroup>(
1106         [](tbb::task_group& /*outer*/) {
1107             volatile bool suppress_unreachable_code_warning = true;
1108             if (suppress_unreachable_code_warning) {
1109                 throw int();
1110             }
1111         },
1112         [](tbb::task_group& outer) {
1113             try {
1114                 outer.wait();
1115                 return tbb::task_group_status::complete;
1116             } catch(const int&) {
1117                 return tbb::task_group_status::canceled;
1118             }
1119         }
1120     );
1121 #endif
1122 }
1123 
1124 } // namespace accept_task_group_context
1125 
1126 //! Respect task_group_context passed from outside
1127 //! \brief \ref interface \ref requirement
1128 TEST_CASE("Respect task_group_context passed from outside") {
1129     accept_task_group_context::test<tbb::task_group>();
1130 #if TBB_PREVIEW_ISOLATED_TASK_GROUP
1131     accept_task_group_context::test<tbb::isolated_task_group>();
1132 #endif
1133 }
1134 #endif // TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1135 
1136 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1137 //! Test checks that for lost task handle
1138 //! \brief \ref requirement
1139 TEST_CASE("Task handle created but not run"){
1140     {
1141         tbb::task_group tg;
1142 
1143         std::atomic<bool> run {false};
1144 
1145         auto h = tg.defer([&]{
1146             run = true;
1147         });
1148         CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
1149     }
1150 }
1151 
1152 //! Basic test for task handle
1153 //! \brief \ref interface \ref requirement
1154 TEST_CASE("Task handle run"){
1155     tbb::task_handle h;
1156 
1157     tbb::task_group tg;
1158     std::atomic<bool> run {false};
1159 
1160     h = tg.defer([&]{
1161         run = true;
1162     });
1163     CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
1164     tg.run(std::move(h));
1165     tg.wait();
1166     CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
1167 
1168     CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
1169 }
1170 
1171 //! Basic test for task handle
1172 //! \brief \ref interface \ref requirement
1173 TEST_CASE("Task handle run_and_wait"){
1174     tbb::task_handle h;
1175 
1176     tbb::task_group tg;
1177     bool run {false};
1178 
1179     h = tg.defer([&]{
1180         run = true;
1181     });
1182     CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called");
1183     tg.run_and_wait(std::move(h));
1184     CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits");
1185 
1186     CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once");
1187 }
1188 //! Test for empty check
1189 //! \brief \ref interface
1190 TEST_CASE("Task handle empty check"){
1191     tbb::task_group tg;
1192 
1193     tbb::task_handle h;
1194 
1195     bool empty = (h == nullptr);
1196     CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
1197 
1198     h = tg.defer([]{});
1199 
1200     CHECK_MESSAGE(h != nullptr, "delayed task returned by task_group::delayed should not be empty");
1201 }
1202 
1203 //! Test for comparison operations
1204 //! \brief \ref interface
1205 TEST_CASE("Task handle comparison/empty checks"){
1206     tbb::task_group tg;
1207 
1208     tbb::task_handle h;
1209 
1210     bool empty =  ! static_cast<bool>(h);
1211     CHECK_MESSAGE(empty, "default constructed task_handle should be empty");
1212     CHECK_MESSAGE(h == nullptr, "default constructed task_handle should be empty");
1213     CHECK_MESSAGE(nullptr == h, "default constructed task_handle should be empty");
1214 
1215     h = tg.defer([]{});
1216 
1217     CHECK_MESSAGE(h != nullptr, "deferred task returned by task_group::defer() should not be empty");
1218     CHECK_MESSAGE(nullptr != h, "deferred task returned by task_group::defer() should not be empty");
1219 
1220 }
1221 
1222 //! Test that task_handle prolongs task_group::wait
1223 //! \brief \ref requirement
1224 TEST_CASE("Task handle blocks wait"){
1225     tbb::task_group tg;
1226 
1227     std::atomic<bool> completed  {false};
1228     std::atomic<bool> start_wait {false};
1229     std::atomic<bool> thread_started{false};
1230 
1231     tbb::task_handle h = tg.defer([&]{
1232         completed = true;
1233     });
1234 
1235     std::thread wait_thread {[&]{
1236         CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
1237 
1238         thread_started = true;
1239         utils::SpinWaitUntilEq(start_wait, true);
1240         tg.wait();
1241         CHECK_MESSAGE(completed == true, "Deferred task should be completed when task_group::wait exits");
1242     }};
1243 
1244     utils::SpinWaitUntilEq(thread_started, true);
1245     CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called");
1246 
1247     tg.run(std::move(h));
1248     //TODO: more accurate test (with fixed number of threads (1 ?) to guarantee correctness of following assert)
1249     //CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) and wait is called");
1250     start_wait = true;
1251     wait_thread.join();
1252 }
1253 
1254 //! The test for task_handle inside other task waiting with run
1255 //! \brief \ref requirement
1256 TEST_CASE("Task handle for scheduler bypass"){
1257     tbb::task_group tg;
1258     std::atomic<bool> run {false};
1259 
1260     tg.run([&]{
1261         return tg.defer([&]{
1262             run = true;
1263         });
1264     });
1265 
1266     tg.wait();
1267     CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run");
1268 }
1269 
1270 //! The test for task_handle inside other task waiting with run_and_wait
1271 //! \brief \ref requirement
1272 TEST_CASE("Task handle for scheduler bypass via run_and_wait"){
1273     tbb::task_group tg;
1274     std::atomic<bool> run {false};
1275 
1276     tg.run_and_wait([&]{
1277         return tg.defer([&]{
1278             run = true;
1279         });
1280     });
1281 
1282     CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run");
1283 }
1284 
1285 #if TBB_USE_EXCEPTIONS
1286 //! The test for exception handling in task_handle
1287 //! \brief \ref requirement
1288 TEST_CASE("Task handle exception propagation"){
1289     tbb::task_group tg;
1290 
1291     tbb::task_handle h = tg.defer([&]{
1292         volatile bool suppress_unreachable_code_warning = true;
1293         if (suppress_unreachable_code_warning) {
1294             throw std::runtime_error{ "" };
1295         }
1296     });
1297 
1298     tg.run(std::move(h));
1299 
1300     CHECK_THROWS_AS(tg.wait(), std::runtime_error);
1301 }
1302 
1303 //! The test for error in scheduling empty task_handle
1304 //! \brief \ref requirement
1305 TEST_CASE("Empty task_handle cannot be scheduled"){
1306     tbb::task_group tg;
1307 
1308     CHECK_THROWS_WITH_AS(tg.run(tbb::task_handle{}), "Attempt to schedule empty task_handle", std::runtime_error);
1309 }
1310 
1311 //! The test for error in task_handle being scheduled into task_group different from one it was created from
1312 //! \brief \ref requirement
1313 TEST_CASE("task_handle cannot be scheduled into different task_group"){
1314     tbb::task_group tg;
1315     tbb::task_group tg1;
1316 
1317     CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error);
1318 }
1319 
1320 //! The test for error in task_handle being scheduled into task_group different from one it was created from
1321 //! \brief \ref requirement
1322 TEST_CASE("task_handle cannot be scheduled into other task_group of the same context"
1323         * doctest::should_fail()    //Implementation is no there yet, as it is not clear that is the expected behavior
1324         * doctest::skip()           //skip the test for now, to not pollute the test log
1325 )
1326 {
1327     tbb::task_group_context ctx;
1328 
1329     tbb::task_group tg(ctx);
1330     tbb::task_group tg1(ctx);
1331 
1332     CHECK_NOTHROW(tg.run(tg.defer([]{})));
1333     CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error);
1334 }
1335 
1336 #endif // TBB_USE_EXCEPTIONS
1337 #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS
1338