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 //! 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 783 //! Fibonacci test for task group 784 //! \brief \ref interface \ref requirement 785 TEST_CASE("Fibonacci test for the task group") { 786 for (unsigned p=MinThread; p <= MaxThread; ++p) { 787 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 788 g_MaxConcurrency = p; 789 RunFibonacciTests<tbb::task_group>(); 790 } 791 } 792 793 //! Cancellation and exception test for the task group 794 //! \brief \ref interface \ref requirement 795 TEST_CASE("Cancellation and exception test for the task group") { 796 for (unsigned p = MinThread; p <= MaxThread; ++p) { 797 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 798 tbb::task_arena a(p); 799 g_MaxConcurrency = p; 800 a.execute([] { 801 RunCancellationAndExceptionHandlingTests<tbb::task_group>(); 802 }); 803 } 804 } 805 806 //! Constant functor test for the task group 807 //! \brief \ref interface \ref negative 808 TEST_CASE("Constant functor test for the task group") { 809 for (unsigned p=MinThread; p <= MaxThread; ++p) { 810 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 811 g_MaxConcurrency = p; 812 TestConstantFunctorRequirement<tbb::task_group>(); 813 } 814 } 815 816 //! Move semantics test for the task group 817 //! \brief \ref interface \ref requirement 818 TEST_CASE("Move semantics test for the task group") { 819 for (unsigned p=MinThread; p <= MaxThread; ++p) { 820 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 821 g_MaxConcurrency = p; 822 TestMoveSemantics<tbb::task_group>(); 823 } 824 } 825 826 #if TBB_PREVIEW_ISOLATED_TASK_GROUP 827 //! Test for thread safety for the isolated_task_group 828 //! \brief \ref error_guessing 829 TEST_CASE("Thread safety test for the isolated task group") { 830 if (tbb::this_task_arena::max_concurrency() < 2) { 831 // The test requires more than one thread to check thread safety 832 return; 833 } 834 for (unsigned p=MinThread; p <= MaxThread; ++p) { 835 if (p < 2) { 836 continue; 837 } 838 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 839 g_MaxConcurrency = p; 840 TestThreadSafety<tbb::isolated_task_group>(); 841 } 842 } 843 844 //! Cancellation and exception test for the isolated task group 845 //! \brief \ref interface \ref requirement 846 TEST_CASE("Fibonacci test for the isolated task group") { 847 for (unsigned p=MinThread; p <= MaxThread; ++p) { 848 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 849 g_MaxConcurrency = p; 850 RunFibonacciTests<tbb::isolated_task_group>(); 851 } 852 } 853 854 //! Cancellation and exception test for the isolated task group 855 //! \brief \ref interface \ref requirement 856 TEST_CASE("Cancellation and exception test for the isolated task group") { 857 for (unsigned p=MinThread; p <= MaxThread; ++p) { 858 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 859 g_MaxConcurrency = p; 860 RunCancellationAndExceptionHandlingTests<tbb::isolated_task_group>(); 861 } 862 } 863 864 //! Constant functor test for the isolated task group. 865 //! \brief \ref interface \ref negative 866 TEST_CASE("Constant functor test for the isolated task group") { 867 for (unsigned p=MinThread; p <= MaxThread; ++p) { 868 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 869 g_MaxConcurrency = p; 870 TestConstantFunctorRequirement<tbb::isolated_task_group>(); 871 } 872 } 873 874 //! Move semantics test for the isolated task group. 875 //! \brief \ref interface \ref requirement 876 TEST_CASE("Move semantics test for the isolated task group") { 877 for (unsigned p=MinThread; p <= MaxThread; ++p) { 878 tbb::global_control limit(tbb::global_control::max_allowed_parallelism, p); 879 g_MaxConcurrency = p; 880 TestMoveSemantics<tbb::isolated_task_group>(); 881 } 882 } 883 #endif /* TBB_PREVIEW_ISOLATED_TASK_GROUP */ 884 885 void run_deep_stealing(tbb::task_group& tg1, tbb::task_group& tg2, int num_tasks, std::atomic<int>& tasks_executed) { 886 for (int i = 0; i < num_tasks; ++i) { 887 tg2.run([&tg1, &tasks_executed] { 888 volatile char consume_stack[1000]{}; 889 ++tasks_executed; 890 tg1.wait(); 891 utils::suppress_unused_warning(consume_stack); 892 }); 893 } 894 } 895 896 // TODO: move to the conformance test 897 //! Test for stack overflow avoidance mechanism. 898 //! \brief \ref requirement 899 TEST_CASE("Test for stack overflow avoidance mechanism") { 900 if (tbb::this_task_arena::max_concurrency() < 2) { 901 return; 902 } 903 904 tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2); 905 tbb::task_group tg1; 906 tbb::task_group tg2; 907 std::atomic<int> tasks_executed{}; 908 tg1.run_and_wait([&tg1, &tg2, &tasks_executed] { 909 run_deep_stealing(tg1, tg2, 10000, tasks_executed); 910 while (tasks_executed < 100) { 911 // Some stealing is expected to happen. 912 utils::yield(); 913 } 914 CHECK(tasks_executed < 10000); 915 }); 916 tg2.wait(); 917 CHECK(tasks_executed == 10000); 918 } 919 920 //! Test for stack overflow avoidance mechanism. 921 //! \brief \ref error_guessing 922 TEST_CASE("Test for stack overflow avoidance mechanism within arena") { 923 if (tbb::this_task_arena::max_concurrency() < 2) { 924 return; 925 } 926 927 tbb::global_control thread_limit(tbb::global_control::max_allowed_parallelism, 2); 928 tbb::task_group tg1; 929 tbb::task_group tg2; 930 std::atomic<int> tasks_executed{}; 931 932 // Determine nested task execution limit. 933 int second_thread_executed{}; 934 tg1.run_and_wait([&tg1, &tg2, &tasks_executed, &second_thread_executed] { 935 run_deep_stealing(tg1, tg2, 10000, tasks_executed); 936 do { 937 second_thread_executed = tasks_executed; 938 utils::Sleep(10); 939 } while (second_thread_executed < 100 || second_thread_executed != tasks_executed); 940 CHECK(tasks_executed < 10000); 941 }); 942 tg2.wait(); 943 CHECK(tasks_executed == 10000); 944 945 tasks_executed = 0; 946 tbb::task_arena a(2, 2); 947 tg1.run_and_wait([&a, &tg1, &tg2, &tasks_executed, second_thread_executed] { 948 run_deep_stealing(tg1, tg2, second_thread_executed-1, tasks_executed); 949 while (tasks_executed < second_thread_executed-1) { 950 // Wait until the second thread near the limit. 951 utils::yield(); 952 } 953 tg2.run([&a, &tg1, &tasks_executed] { 954 a.execute([&tg1, &tasks_executed] { 955 volatile char consume_stack[1000]{}; 956 ++tasks_executed; 957 tg1.wait(); 958 utils::suppress_unused_warning(consume_stack); 959 }); 960 }); 961 while (tasks_executed < second_thread_executed) { 962 // Wait until the second joins the arena. 963 utils::yield(); 964 } 965 a.execute([&tg1, &tg2, &tasks_executed] { 966 run_deep_stealing(tg1, tg2, 10000, tasks_executed); 967 }); 968 int currently_executed{}; 969 do { 970 currently_executed = tasks_executed; 971 utils::Sleep(10); 972 } while (currently_executed != tasks_executed); 973 CHECK(tasks_executed < 10000 + second_thread_executed); 974 }); 975 a.execute([&tg2] { 976 tg2.wait(); 977 }); 978 CHECK(tasks_executed == 10000 + second_thread_executed); 979 } 980 981 //! Test checks that we can submit work to task_group asynchronously with waiting. 982 //! \brief \ref regression 983 TEST_CASE("Async task group") { 984 int num_threads = tbb::this_task_arena::max_concurrency(); 985 if (num_threads < 3) { 986 // The test requires at least 2 worker threads 987 return; 988 } 989 tbb::task_arena a(2*num_threads, num_threads); 990 utils::SpinBarrier barrier(num_threads + 2); 991 tbb::task_group tg[2]; 992 std::atomic<bool> finished[2]{}; 993 finished[0] = false; finished[1] = false; 994 for (int i = 0; i < 2; ++i) { 995 a.enqueue([i, &tg, &finished, &barrier] { 996 barrier.wait(); 997 for (int j = 0; j < 10000; ++j) { 998 tg[i].run([] {}); 999 utils::yield(); 1000 } 1001 finished[i] = true; 1002 }); 1003 } 1004 utils::NativeParallelFor(num_threads, [&](int idx) { 1005 barrier.wait(); 1006 a.execute([idx, &tg, &finished] { 1007 std::size_t counter{}; 1008 while (!finished[idx%2]) { 1009 tg[idx%2].wait(); 1010 if (counter++ % 16 == 0) utils::yield(); 1011 } 1012 tg[idx%2].wait(); 1013 }); 1014 }); 1015 } 1016 1017 struct SelfRunner { 1018 tbb::task_group& m_tg; 1019 std::atomic<unsigned>& count; 1020 void operator()() const { 1021 unsigned previous_count = count.fetch_sub(1); 1022 if (previous_count > 1) 1023 m_tg.run( *this ); 1024 } 1025 }; 1026 1027 //! Submit work to single task_group instance from inside the work 1028 //! \brief \ref error_guessing 1029 TEST_CASE("Run self using same task_group instance") { 1030 const unsigned num = 10; 1031 std::atomic<unsigned> count{num}; 1032 tbb::task_group tg; 1033 SelfRunner uf{tg, count}; 1034 tg.run( uf ); 1035 tg.wait(); 1036 CHECK_MESSAGE( 1037 count == 0, 1038 "Not all tasks were spawned from inside the functor running within task_group." 1039 ); 1040 } 1041 1042 #if TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1043 1044 namespace accept_task_group_context { 1045 1046 template <typename TaskGroup, typename CancelF, typename WaitF> 1047 void run_cancellation_use_case(CancelF&& cancel, WaitF&& wait) { 1048 std::atomic<bool> outer_cancelled{false}; 1049 std::atomic<unsigned> count{13}; 1050 1051 tbb::task_group_context inner_ctx(tbb::task_group_context::isolated); 1052 TaskGroup inner_tg(inner_ctx); 1053 1054 tbb::task_group outer_tg; 1055 auto outer_tg_task = [&] { 1056 inner_tg.run([&] { 1057 utils::SpinWaitUntilEq(outer_cancelled, true); 1058 inner_tg.run( SelfRunner{inner_tg, count} ); 1059 }); 1060 1061 utils::try_call([&] { 1062 std::forward<CancelF>(cancel)(outer_tg); 1063 }).on_completion([&] { 1064 outer_cancelled = true; 1065 }); 1066 }; 1067 1068 auto check = [&] { 1069 tbb::task_group_status outer_status = tbb::task_group_status::not_complete; 1070 outer_status = std::forward<WaitF>(wait)(outer_tg); 1071 CHECK_MESSAGE( 1072 outer_status == tbb::task_group_status::canceled, 1073 "Outer task group should have been cancelled." 1074 ); 1075 1076 tbb::task_group_status inner_status = inner_tg.wait(); 1077 CHECK_MESSAGE( 1078 inner_status == tbb::task_group_status::complete, 1079 "Inner task group should have completed despite the cancellation of the outer one." 1080 ); 1081 1082 CHECK_MESSAGE(0 == count, "Some of the inner group tasks were not executed."); 1083 }; 1084 1085 outer_tg.run(outer_tg_task); 1086 check(); 1087 } 1088 1089 template <typename TaskGroup> 1090 void test() { 1091 run_cancellation_use_case<TaskGroup>( 1092 [](tbb::task_group& outer) { outer.cancel(); }, 1093 [](tbb::task_group& outer) { return outer.wait(); } 1094 ); 1095 1096 #if TBB_USE_EXCEPTIONS 1097 run_cancellation_use_case<TaskGroup>( 1098 [](tbb::task_group& /*outer*/) { 1099 volatile bool suppress_unreachable_code_warning = true; 1100 if (suppress_unreachable_code_warning) { 1101 throw int(); 1102 } 1103 }, 1104 [](tbb::task_group& outer) { 1105 try { 1106 outer.wait(); 1107 return tbb::task_group_status::complete; 1108 } catch(const int&) { 1109 return tbb::task_group_status::canceled; 1110 } 1111 } 1112 ); 1113 #endif 1114 } 1115 1116 } // namespace accept_task_group_context 1117 1118 //! Respect task_group_context passed from outside 1119 //! \brief \ref interface \ref requirement 1120 TEST_CASE("Respect task_group_context passed from outside") { 1121 accept_task_group_context::test<tbb::task_group>(); 1122 #if TBB_PREVIEW_ISOLATED_TASK_GROUP 1123 accept_task_group_context::test<tbb::isolated_task_group>(); 1124 #endif 1125 } 1126 #endif // TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1127 1128 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1129 //! Test checks that for lost task handle 1130 //! \brief \ref requirement 1131 TEST_CASE("Task handle created but not run"){ 1132 { 1133 tbb::task_group tg; 1134 1135 std::atomic<bool> run {false}; 1136 1137 auto h = tg.defer([&]{ 1138 run = true; 1139 }); 1140 CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called"); 1141 } 1142 } 1143 1144 //! Basic test for task handle 1145 //! \brief \ref interface \ref requirement 1146 TEST_CASE("Task handle run"){ 1147 tbb::task_handle h; 1148 1149 tbb::task_group tg; 1150 std::atomic<bool> run {false}; 1151 1152 h = tg.defer([&]{ 1153 run = true; 1154 }); 1155 CHECK_MESSAGE(run == false, "delayed task should not be run until run(task_handle) is called"); 1156 tg.run(std::move(h)); 1157 tg.wait(); 1158 CHECK_MESSAGE(run == true, "Delayed task should be completed when task_group::wait exits"); 1159 1160 CHECK_MESSAGE(h == nullptr, "Delayed task can be executed only once"); 1161 } 1162 1163 //! Test for empty check 1164 //! \brief \ref interface 1165 TEST_CASE("Task handle empty check"){ 1166 tbb::task_group tg; 1167 1168 tbb::task_handle h; 1169 1170 bool empty = (h == nullptr); 1171 CHECK_MESSAGE(empty, "default constructed task_handle should be empty"); 1172 1173 h = tg.defer([]{}); 1174 1175 CHECK_MESSAGE(h != nullptr, "delayed task returned by task_group::delayed should not be empty"); 1176 } 1177 1178 //! Test for comparison operations 1179 //! \brief \ref interface 1180 TEST_CASE("Task handle comparison/empty checks"){ 1181 tbb::task_group tg; 1182 1183 tbb::task_handle h; 1184 1185 bool empty = ! static_cast<bool>(h); 1186 CHECK_MESSAGE(empty, "default constructed task_handle should be empty"); 1187 CHECK_MESSAGE(h == nullptr, "default constructed task_handle should be empty"); 1188 CHECK_MESSAGE(nullptr == h, "default constructed task_handle should be empty"); 1189 1190 h = tg.defer([]{}); 1191 1192 CHECK_MESSAGE(h != nullptr, "deferred task returned by task_group::defer() should not be empty"); 1193 CHECK_MESSAGE(nullptr != h, "deferred task returned by task_group::defer() should not be empty"); 1194 1195 } 1196 1197 //! Test that task_handle prolongs task_group::wait 1198 //! \brief \ref requirement 1199 TEST_CASE("Task handle blocks wait"){ 1200 tbb::task_group tg; 1201 1202 std::atomic<bool> completed {false}; 1203 std::atomic<bool> start_wait {false}; 1204 std::atomic<bool> thread_started{false}; 1205 1206 tbb::task_handle h = tg.defer([&]{ 1207 completed = true; 1208 }); 1209 1210 std::thread wait_thread {[&]{ 1211 CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called"); 1212 1213 thread_started = true; 1214 utils::SpinWaitUntilEq(start_wait, true); 1215 tg.wait(); 1216 CHECK_MESSAGE(completed == true, "Deferred task should be completed when task_group::wait exits"); 1217 }}; 1218 1219 utils::SpinWaitUntilEq(thread_started, true); 1220 CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) is called"); 1221 1222 tg.run(std::move(h)); 1223 //TODO: more accurate test (with fixed number of threads (1 ?) to guarantee correctness of following assert) 1224 //CHECK_MESSAGE(completed == false, "Deferred task should not be run until run(task_handle) and wait is called"); 1225 start_wait = true; 1226 wait_thread.join(); 1227 } 1228 1229 //! The test for task_handle inside other task waiting with run 1230 //! \brief \ref requirement 1231 TEST_CASE("Task handle for scheduler bypass"){ 1232 tbb::task_group tg; 1233 std::atomic<bool> run {false}; 1234 1235 tg.run([&]{ 1236 return tg.defer([&]{ 1237 run = true; 1238 }); 1239 }); 1240 1241 tg.wait(); 1242 CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run"); 1243 } 1244 1245 //! The test for task_handle inside other task waiting with run_and_wait 1246 //! \brief \ref requirement 1247 TEST_CASE("Task handle for scheduler bypass via run_and_wait"){ 1248 tbb::task_group tg; 1249 std::atomic<bool> run {false}; 1250 1251 tg.run_and_wait([&]{ 1252 return tg.defer([&]{ 1253 run = true; 1254 }); 1255 }); 1256 1257 CHECK_MESSAGE(run == true, "task handle returned by user lambda (bypassed) should be run"); 1258 } 1259 1260 #if TBB_USE_EXCEPTIONS 1261 //! The test for exception handling in task_handle 1262 //! \brief \ref requirement 1263 TEST_CASE("Task handle exception propagation"){ 1264 tbb::task_group tg; 1265 1266 tbb::task_handle h = tg.defer([&]{ 1267 volatile bool suppress_unreachable_code_warning = true; 1268 if (suppress_unreachable_code_warning) { 1269 throw std::runtime_error{ "" }; 1270 } 1271 }); 1272 1273 tg.run(std::move(h)); 1274 1275 CHECK_THROWS_AS(tg.wait(), std::runtime_error); 1276 } 1277 1278 //! The test for error in scheduling empty task_handle 1279 //! \brief \ref requirement 1280 TEST_CASE("Empty task_handle cannot be scheduled"){ 1281 tbb::task_group tg; 1282 1283 CHECK_THROWS_WITH_AS(tg.run(tbb::task_handle{}), "Attempt to schedule empty task_handle", std::runtime_error); 1284 } 1285 1286 //! The test for error in task_handle being scheduled into task_group different from one it was created from 1287 //! \brief \ref requirement 1288 TEST_CASE("task_handle cannot be scheduled into different task_group"){ 1289 tbb::task_group tg; 1290 tbb::task_group tg1; 1291 1292 CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error); 1293 } 1294 1295 //! The test for error in task_handle being scheduled into task_group different from one it was created from 1296 //! \brief \ref requirement 1297 TEST_CASE("task_handle cannot be scheduled into other task_group of the same context" 1298 * doctest::should_fail() //Implementation is no there yet, as it is not clear that is the expected behaviour 1299 * doctest::skip() //skip the test for now, to not pollute the test log 1300 ) 1301 { 1302 tbb::task_group_context ctx; 1303 1304 tbb::task_group tg(ctx); 1305 tbb::task_group tg1(ctx); 1306 1307 CHECK_NOTHROW(tg.run(tg.defer([]{}))); 1308 CHECK_THROWS_WITH_AS(tg1.run(tg.defer([]{})), "Attempt to schedule task_handle into different task_group", std::runtime_error); 1309 } 1310 1311 #endif // TBB_USE_EXCEPTIONS 1312 #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS 1313