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