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