1/*
2 Copyright (c) 2005-2019 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#include "harness_defs.h"
18
19//Concurrency scheduler is not supported by Windows* new UI apps
20//TODO: check whether we can test anything here
21#include "tbb/tbb_config.h"
22#if !__TBB_WIN8UI_SUPPORT
23#ifndef TBBTEST_USE_TBB
24 #define TBBTEST_USE_TBB 1
25#endif
26#else
27 #define TBBTEST_USE_TBB 0
28 #undef __TBB_TASK_GROUP_CONTEXT
29 #define __TBB_TASK_GROUP_CONTEXT 0
30#endif
31
32#if !TBBTEST_USE_TBB
33 #if defined(_MSC_VER) && _MSC_VER < 1600
34 #ifdef TBBTEST_USE_TBB
35 #undef TBBTEST_USE_TBB
36 #endif
37 #define TBBTEST_USE_TBB 1
38 #endif
39#endif
40
41#if TBBTEST_USE_TBB
42
43 #include "tbb/compat/ppl.h"
44 #include "tbb/task_scheduler_init.h"
45
46 #if _MSC_VER
47 typedef tbb::internal::uint32_t uint_t;
48 #else
49 typedef uint32_t uint_t;
50 #endif
51
52#else /* !TBBTEST_USE_TBB */
53
54 #if defined(_MSC_VER)
55 #pragma warning(disable: 4100 4180)
56 #endif
57
58 #include <ppl.h>
59
60 typedef unsigned int uint_t;
61
62 // Bug in this ConcRT version results in task_group::wait() rethrowing
63 // internal cancellation exception propagated by the scheduler from the nesting
64 // task group.
65 #define __TBB_SILENT_CANCELLATION_BROKEN (_MSC_VER == 1600)
66
67#endif /* !TBBTEST_USE_TBB */
68
69#if __TBB_TASK_GROUP_CONTEXT
70
71#include "tbb/atomic.h"
72#include "tbb/aligned_space.h"
73#include "harness.h"
74#include "harness_concurrency_tracker.h"
75
76unsigned g_MaxConcurrency = 0;
77
78typedef tbb::atomic<uint_t> atomic_t;
79typedef Concurrency::task_handle<void(*)()> handle_type;
80
81//------------------------------------------------------------------------
82// Tests for the thread safety of the task_group manipulations
83//------------------------------------------------------------------------
84
85#include "harness_barrier.h"
86
87enum SharingMode {
88 VagabondGroup = 1,
89 ParallelWait = 2
90};
91
92class SharedGroupBodyImpl : NoCopy, Harness::NoAfterlife {
93 static const uint_t c_numTasks0 = 4096,
94 c_numTasks1 = 1024;
95
96 const uint_t m_numThreads;
97 const uint_t m_sharingMode;
98
99 Concurrency::task_group *m_taskGroup;
100 atomic_t m_tasksSpawned,
101 m_threadsReady;
102 Harness::SpinBarrier m_barrier;
103
104 static atomic_t s_tasksExecuted;
105
106 struct TaskFunctor {
107 SharedGroupBodyImpl *m_pOwner;
108 void operator () () const {
109 if ( m_pOwner->m_sharingMode & ParallelWait ) {
110 while ( Harness::ConcurrencyTracker::PeakParallelism() < m_pOwner->m_numThreads )
111 __TBB_Yield();
112 }
113 ++s_tasksExecuted;
114 }
115 };
116
117 TaskFunctor m_taskFunctor;
118
119 void Spawn ( uint_t numTasks ) {
120 for ( uint_t i = 0; i < numTasks; ++i ) {
121 ++m_tasksSpawned;
122 Harness::ConcurrencyTracker ct;
123 m_taskGroup->run( m_taskFunctor );
124 }
125 ++m_threadsReady;
126 }
127
128 void DeleteTaskGroup () {
129 delete m_taskGroup;
130 m_taskGroup = NULL;
131 }
132
133 void Wait () {
134 while ( m_threadsReady != m_numThreads )
135 __TBB_Yield();
136 const uint_t numSpawned = c_numTasks0 + c_numTasks1 * (m_numThreads - 1);
137 ASSERT ( m_tasksSpawned == numSpawned, "Wrong number of spawned tasks. The test is broken" );
138 REMARK("Max spawning parallelism is %u out of %u\n", Harness::ConcurrencyTracker::PeakParallelism(), g_MaxConcurrency);
139 if ( m_sharingMode & ParallelWait ) {
140 m_barrier.wait( &Harness::ConcurrencyTracker::Reset );
141 {
142 Harness::ConcurrencyTracker ct;
143 m_taskGroup->wait();
144 }
145 if ( Harness::ConcurrencyTracker::PeakParallelism() == 1 )
146 REPORT ( "Warning: No parallel waiting detected in TestParallelWait\n" );
147 m_barrier.wait();
148 }
149 else
150 m_taskGroup->wait();
151 ASSERT ( m_tasksSpawned == numSpawned, "No tasks should be spawned after wait starts. The test is broken" );
152 ASSERT ( s_tasksExecuted == numSpawned, "Not all spawned tasks were executed" );
153 }
154
155public:
156 SharedGroupBodyImpl ( uint_t numThreads, uint_t sharingMode = 0 )
157 : m_numThreads(numThreads)
158 , m_sharingMode(sharingMode)
159 , m_taskGroup(NULL)
160 , m_barrier(numThreads)
161 {
162 ASSERT ( m_numThreads > 1, "SharedGroupBody tests require concurrency" );
163 ASSERT ( !(m_sharingMode & VagabondGroup) || m_numThreads == 2, "In vagabond mode SharedGroupBody must be used with 2 threads only" );
164 Harness::ConcurrencyTracker::Reset();
165 s_tasksExecuted = 0;
166 m_tasksSpawned = 0;
167 m_threadsReady = 0;
168 m_taskFunctor.m_pOwner = this;
169 }
170
171 void Run ( uint_t idx ) {
172#if TBBTEST_USE_TBB
173 tbb::task_scheduler_init init(g_MaxConcurrency);
174#endif
175 AssertLive();
176 if ( idx == 0 ) {
177 ASSERT ( !m_taskGroup && !m_tasksSpawned, "SharedGroupBody must be reset before reuse");
178 m_taskGroup = new Concurrency::task_group;
179 Spawn( c_numTasks0 );
180 Wait();
181 if ( m_sharingMode & VagabondGroup )
182 m_barrier.wait();
183 else
184 DeleteTaskGroup();
185 }
186 else {
187 while ( m_tasksSpawned == 0 )
188 __TBB_Yield();
189 ASSERT ( m_taskGroup, "Task group is not initialized");
190 Spawn (c_numTasks1);
191 if ( m_sharingMode & ParallelWait )
192 Wait();
193 if ( m_sharingMode & VagabondGroup ) {
194 ASSERT ( idx == 1, "In vagabond mode SharedGroupBody must be used with 2 threads only" );
195 m_barrier.wait();
196 DeleteTaskGroup();
197 }
198 }
199 AssertLive();
200 }
201};
202
203atomic_t SharedGroupBodyImpl::s_tasksExecuted;
204
205class SharedGroupBody : NoAssign, Harness::NoAfterlife {
206 bool m_bOwner;
207 SharedGroupBodyImpl *m_pImpl;
208public:
209 SharedGroupBody ( uint_t numThreads, uint_t sharingMode = 0 )
210 : m_bOwner(true)
211 , m_pImpl( new SharedGroupBodyImpl(numThreads, sharingMode) )
212 {}
213 SharedGroupBody ( const SharedGroupBody& src )
214 : NoAssign()
215 , Harness::NoAfterlife()
216 , m_bOwner(false)
217 , m_pImpl(src.m_pImpl)
218 {}
219 ~SharedGroupBody () {
220 if ( m_bOwner )
221 delete m_pImpl;
222 }
223 void operator() ( uint_t idx ) const { m_pImpl->Run(idx); }
224};
225
226class RunAndWaitSyncronizationTestBody : NoAssign {
227 Harness::SpinBarrier& m_barrier;
228 tbb::atomic<bool>& m_completed;
229 tbb::task_group& m_tg;
230public:
231 RunAndWaitSyncronizationTestBody(Harness::SpinBarrier& barrier, tbb::atomic<bool>& completed, tbb::task_group& tg)
232 : m_barrier(barrier), m_completed(completed), m_tg(tg) {}
233
234 void operator()() const {
235 m_barrier.wait();
236 for (volatile int i = 0; i < 100000; ++i) {}
237 m_completed = true;
238 }
239
240 void operator()(int id) const {
241 if (id == 0) {
242 m_tg.run_and_wait(*this);
243 } else {
244 m_barrier.wait();
245 m_tg.wait();
246 ASSERT(m_completed, "A concurrent waiter has left the wait method earlier than work has finished");
247 }
248 }
249};
250
251void TestParallelSpawn () {
252 NativeParallelFor( g_MaxConcurrency, SharedGroupBody(g_MaxConcurrency) );
253}
254
255void TestParallelWait () {
256 NativeParallelFor( g_MaxConcurrency, SharedGroupBody(g_MaxConcurrency, ParallelWait) );
257
258 Harness::SpinBarrier barrier(g_MaxConcurrency);
259 tbb::atomic<bool> completed;
260 completed = false;
261 tbb::task_group tg;
262 RunAndWaitSyncronizationTestBody b(barrier, completed, tg);
263 NativeParallelFor( g_MaxConcurrency, b );
264}
265
266// Tests non-stack-bound task group (the group that is allocated by one thread and destroyed by the other)
267void TestVagabondGroup () {
268 NativeParallelFor( 2, SharedGroupBody(2, VagabondGroup) );
269}
270
271//------------------------------------------------------------------------
272// Common requisites of the Fibonacci tests
273//------------------------------------------------------------------------
274
275const uint_t N = 20;
276const uint_t F = 6765;
277
278atomic_t g_Sum;
279
280#define FIB_TEST_PROLOGUE() \
281 const unsigned numRepeats = g_MaxConcurrency * (TBB_USE_DEBUG ? 4 : 16); \
282 Harness::ConcurrencyTracker::Reset()
283
284#define FIB_TEST_EPILOGUE(sum) \
285 ASSERT( sum == numRepeats * F, NULL ); \
286 REMARK("Realized parallelism in Fib test is %u out of %u\n", Harness::ConcurrencyTracker::PeakParallelism(), g_MaxConcurrency)
287
288//------------------------------------------------------------------------
289// Test for a complex tree of task groups
290//
291// The test executes a tree of task groups of the same sort with asymmetric
292// descendant nodes distribution at each level at each level.
293//
294// The chores are specified as functor objects. Each task group contains only one chore.
295//------------------------------------------------------------------------
296
297template<uint_t Func(uint_t)>
298struct FibTask : NoAssign, Harness::NoAfterlife {
299 uint_t* m_pRes;
300 const uint_t m_Num;
301 FibTask( uint_t* y, uint_t n ) : m_pRes(y), m_Num(n) {}
302 void operator() () const {
303 *m_pRes = Func(m_Num);
304 }
305};
306
307uint_t Fib_SpawnRightChildOnly ( uint_t n ) {
308 Harness::ConcurrencyTracker ct;
309 if( n<2 ) {
310 return n;
311 } else {
312 uint_t y = ~0u;
313 Concurrency::task_group tg;
314 tg.run( FibTask<Fib_SpawnRightChildOnly>(&y, n-1) );
315 uint_t x = Fib_SpawnRightChildOnly(n-2);
316 tg.wait();
317 return y+x;
318 }
319}
320
321void TestFib1 () {
322 FIB_TEST_PROLOGUE();
323 uint_t sum = 0;
324 for( unsigned i = 0; i < numRepeats; ++i )
325 sum += Fib_SpawnRightChildOnly(N);
326 FIB_TEST_EPILOGUE(sum);
327}
328
329
330//------------------------------------------------------------------------
331// Test for a mixed tree of task groups.
332//
333// The test executes a tree with multiple task of one sort at the first level,
334// each of which originates in its turn a binary tree of descendant task groups.
335//
336// The chores are specified both as functor objects and as function pointers
337//------------------------------------------------------------------------
338
339uint_t Fib_SpawnBothChildren( uint_t n ) {
340 Harness::ConcurrencyTracker ct;
341 if( n<2 ) {
342 return n;
343 } else {
344 uint_t y = ~0u,
345 x = ~0u;
346 Concurrency::task_group tg;
347 tg.run( FibTask<Fib_SpawnBothChildren>(&x, n-2) );
348 tg.run( FibTask<Fib_SpawnBothChildren>(&y, n-1) );
349 tg.wait();
350 return y + x;
351 }
352}
353
354void RunFib2 () {
355 g_Sum += Fib_SpawnBothChildren(N);
356}
357
358void TestFib2 () {
359 FIB_TEST_PROLOGUE();
360 g_Sum = 0;
361 Concurrency::task_group rg;
362 for( unsigned i = 0; i < numRepeats - 1; ++i )
363 rg.run( &RunFib2 );
364 rg.wait();
365 rg.run( &RunFib2 );
366 rg.wait();
367 FIB_TEST_EPILOGUE(g_Sum);
368}
369
370
371//------------------------------------------------------------------------
372// Test for a complex tree of task groups
373// The chores are specified as task handles for recursive functor objects.
374//------------------------------------------------------------------------
375
376class FibTask_SpawnRightChildOnly : NoAssign, Harness::NoAfterlife {
377 uint_t* m_pRes;
378 mutable uint_t m_Num;
379
380public:
381 FibTask_SpawnRightChildOnly( uint_t* y, uint_t n ) : m_pRes(y), m_Num(n) {}
382 void operator() () const {
383 Harness::ConcurrencyTracker ct;
384 AssertLive();
385 if( m_Num < 2 ) {
386 *m_pRes = m_Num;
387 } else {
388 uint_t y = ~0u;
389 Concurrency::task_group tg;
390 Concurrency::task_handle<FibTask_SpawnRightChildOnly> h = FibTask_SpawnRightChildOnly(&y, m_Num-1);
391 tg.run( h );
392 m_Num -= 2;
393 tg.run_and_wait( *this );
394 *m_pRes += y;
395 }
396 }
397};
398
399uint_t RunFib3 ( uint_t n ) {
400 uint_t res = ~0u;
401 FibTask_SpawnRightChildOnly func(&res, n);
402 func();
403 return res;
404}
405
406void TestTaskHandle () {
407 FIB_TEST_PROLOGUE();
408 uint_t sum = 0;
409 for( unsigned i = 0; i < numRepeats; ++i )
410 sum += RunFib3(N);
411 FIB_TEST_EPILOGUE(sum);
412}
413
414//------------------------------------------------------------------------
415// Test for a mixed tree of task groups.
416// The chores are specified as task handles for both functor objects and function pointers
417//------------------------------------------------------------------------
418
419template<class task_group_type>
420class FibTask_SpawnBothChildren : NoAssign, Harness::NoAfterlife {
421 uint_t* m_pRes;
422 uint_t m_Num;
423public:
424 FibTask_SpawnBothChildren( uint_t* y, uint_t n ) : m_pRes(y), m_Num(n) {}
425 void operator() () const {
426 Harness::ConcurrencyTracker ct;
427 AssertLive();
428 if( m_Num < 2 ) {
429 *m_pRes = m_Num;
430 } else {
431 uint_t x = ~0u, // initialized only to suppress warning
432 y = ~0u;
433 task_group_type tg;
434 Concurrency::task_handle<FibTask_SpawnBothChildren> h1 = FibTask_SpawnBothChildren(&y, m_Num-1),
435 h2 = FibTask_SpawnBothChildren(&x, m_Num-2);
436 tg.run( h1 );
437 tg.run( h2 );
438 tg.wait();
439 *m_pRes = x + y;
440 }
441 }
442};
443
444template<class task_group_type>
445void RunFib4 () {
446 uint_t res = ~0u;
447 FibTask_SpawnBothChildren<task_group_type> func(&res, N);
448 func();
449 g_Sum += res;
450}
451
452template<class task_group_type>
453void TestTaskHandle2 () {
454 FIB_TEST_PROLOGUE();
455 g_Sum = 0;
456 task_group_type rg;
457 typedef tbb::aligned_space<handle_type> handle_space_t;
458 handle_space_t *handles = new handle_space_t[numRepeats];
459 handle_type *h = NULL;
460#if __TBB_ipf && __TBB_GCC_VERSION==40601
461 volatile // Workaround for unexpected exit from the loop below after the exception was caught
462#endif
463 unsigned i = 0;
464 for( ;; ++i ) {
465 h = handles[i].begin();
466#if __TBB_FUNC_PTR_AS_TEMPL_PARAM_BROKEN
467 new ( h ) handle_type((void(*)())RunFib4<task_group_type>);
468#else
469 new ( h ) handle_type(RunFib4<task_group_type>);
470#endif
471 if ( i == numRepeats - 1 )
472 break;
473 rg.run( *h );
474#if TBB_USE_EXCEPTIONS && !__TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN
475 bool caught = false;
476 try {
477 if( i&1 ) rg.run( *h );
478 else rg.run_and_wait( *h );
479 }
480 catch ( Concurrency::invalid_multiple_scheduling& e ) {
481 ASSERT( e.what(), "Error message is absent" );
482 caught = true;
483 }
484 catch ( ... ) {
485 ASSERT ( __TBB_EXCEPTION_TYPE_INFO_BROKEN, "Unrecognized exception" );
486 }
487 ASSERT ( caught, "Expected invalid_multiple_scheduling exception is missing" );
488#endif /* TBB_USE_EXCEPTIONS && !__TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN */
489 }
490 ASSERT( i == numRepeats - 1, "unexpected exit from the loop" );
491 rg.run_and_wait( *h );
492
493 for( i = 0; i < numRepeats; ++i )
494#if __TBB_UNQUALIFIED_CALL_OF_DTOR_BROKEN
495 handles[i].begin()->Concurrency::task_handle<void(*)()>::~task_handle();
496#else
497 handles[i].begin()->~handle_type();
498#endif
499 delete []handles;
500 FIB_TEST_EPILOGUE(g_Sum);
501}
502
503#if __TBB_CPP11_LAMBDAS_PRESENT
504//------------------------------------------------------------------------
505// Test for a mixed tree of task groups.
506// The chores are specified as lambdas
507//------------------------------------------------------------------------
508
509void TestFibWithLambdas () {
510 REMARK ("Lambdas test");
511 FIB_TEST_PROLOGUE();
512 atomic_t sum;
513 sum = 0;
514 Concurrency::task_group rg;
515 for( unsigned i = 0; i < numRepeats; ++i )
516 rg.run( [&](){sum += Fib_SpawnBothChildren(N);} );
517 rg.wait();
518 FIB_TEST_EPILOGUE(sum);
519}
520
521//------------------------------------------------------------------------
522// Test for make_task.
523// The chores are specified as lambdas converted to task_handles.
524//------------------------------------------------------------------------
525
526void TestFibWithMakeTask () {
527 REMARK ("Make_task test\n");
528 atomic_t sum;
529 sum = 0;
530 Concurrency::task_group rg;
531 const auto &h1 = Concurrency::make_task( [&](){sum += Fib_SpawnBothChildren(N);} );
532 const auto &h2 = Concurrency::make_task( [&](){sum += Fib_SpawnBothChildren(N);} );
533 rg.run( h1 );
534 rg.run_and_wait( h2 );
535 ASSERT( sum == 2 * F, NULL );
536}
537#endif /* __TBB_CPP11_LAMBDAS_PRESENT */
538
539
540//------------------------------------------------------------------------
541// Tests for exception handling and cancellation behavior.
542//------------------------------------------------------------------------
543
544class test_exception : public std::exception
545{
546 const char* m_strDescription;
547public:
548 test_exception ( const char* descr ) : m_strDescription(descr) {}
549
550 const char* what() const throw() __TBB_override { return m_strDescription; }
551};
552
553#if TBB_USE_CAPTURED_EXCEPTION
554 #include "tbb/tbb_exception.h"
555 typedef tbb::captured_exception TestException;
556#else
557 typedef test_exception TestException;
558#endif
559
560#include <string.h>
561
562#define NUM_CHORES 512
563#define NUM_GROUPS 64
564#define SKIP_CHORES (NUM_CHORES/4)
565#define SKIP_GROUPS (NUM_GROUPS/4)
566#define EXCEPTION_DESCR1 "Test exception 1"
567#define EXCEPTION_DESCR2 "Test exception 2"
568
569atomic_t g_ExceptionCount;
570atomic_t g_TaskCount;
571unsigned g_ExecutedAtCancellation;
572bool g_Rethrow;
573bool g_Throw;
574#if __TBB_SILENT_CANCELLATION_BROKEN
575 volatile bool g_CancellationPropagationInProgress;
576 #define CATCH_ANY() \
577 __TBB_CATCH( ... ) { \
578 if ( g_CancellationPropagationInProgress ) { \
579 if ( g_Throw ) { \
580 exceptionCaught = true; \
581 ++g_ExceptionCount; \
582 } \
583 } else \
584 ASSERT( false, "Unknown exception" ); \
585 }
586#else
587 #define CATCH_ANY() __TBB_CATCH( ... ) { ASSERT( __TBB_EXCEPTION_TYPE_INFO_BROKEN, "Unknown exception" ); }
588#endif
589
590inline
591void ResetGlobals ( bool bThrow, bool bRethrow ) {
592 g_Throw = bThrow;
593 g_Rethrow = bRethrow;
594#if __TBB_SILENT_CANCELLATION_BROKEN
595 g_CancellationPropagationInProgress = false;
596#endif
597 g_ExceptionCount = 0;
598 g_TaskCount = 0;
599 Harness::ConcurrencyTracker::Reset();
600}
601
602class ThrowingTask : NoAssign, Harness::NoAfterlife {
603 atomic_t &m_TaskCount;
604public:
605 ThrowingTask( atomic_t& counter ) : m_TaskCount(counter) {}
606 void operator() () const {
607 Harness::ConcurrencyTracker ct;
608 AssertLive();
609 if ( g_Throw ) {
610 if ( ++m_TaskCount == SKIP_CHORES )
611 __TBB_THROW( test_exception(EXCEPTION_DESCR1) );
612 __TBB_Yield();
613 }
614 else {
615 ++g_TaskCount;
616 while( !Concurrency::is_current_task_group_canceling() )
617 __TBB_Yield();
618 }
619 }
620};
621
622void LaunchChildren () {
623 atomic_t count;
624 count = 0;
625 Concurrency::task_group g;
626 bool exceptionCaught = false;
627 for( unsigned i = 0; i < NUM_CHORES; ++i )
628 g.run( ThrowingTask(count) );
629 Concurrency::task_group_status status = Concurrency::not_complete;
630 __TBB_TRY {
631 status = g.wait();
632 } __TBB_CATCH ( TestException& e ) {
633#if TBB_USE_EXCEPTIONS
634 ASSERT( e.what(), "Empty what() string" );
635 ASSERT( __TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(e.what(), EXCEPTION_DESCR1) == 0, "Unknown exception" );
636#endif /* TBB_USE_EXCEPTIONS */
637 exceptionCaught = true;
638 ++g_ExceptionCount;
639 } CATCH_ANY();
640 ASSERT( !g_Throw || exceptionCaught || status == Concurrency::canceled, "No exception in the child task group" );
641 if ( g_Rethrow && g_ExceptionCount > SKIP_GROUPS ) {
642#if __TBB_SILENT_CANCELLATION_BROKEN
643 g_CancellationPropagationInProgress = true;
644#endif
645 __TBB_THROW( test_exception(EXCEPTION_DESCR2) );
646 }
647}
648
649#if TBB_USE_EXCEPTIONS
650void TestEh1 () {
651 ResetGlobals( true, false );
652 Concurrency::task_group rg;
653 for( unsigned i = 0; i < NUM_GROUPS; ++i )
654 // TBB version does not require taking function address
655 rg.run( &LaunchChildren );
656 try {
657 rg.wait();
658 } catch ( ... ) {
659 ASSERT( false, "Unexpected exception" );
660 }
661 ASSERT( g_ExceptionCount <= NUM_GROUPS, "Too many exceptions from the child groups. The test is broken" );
662 ASSERT( g_ExceptionCount == NUM_GROUPS, "Not all child groups threw the exception" );
663}
664
665void TestEh2 () {
666 ResetGlobals( true, true );
667 Concurrency::task_group rg;
668 bool exceptionCaught = false;
669 for( unsigned i = 0; i < NUM_GROUPS; ++i )
670 // TBB version does not require taking function address
671 rg.run( &LaunchChildren );
672 try {
673 rg.wait();
674 } catch ( TestException& e ) {
675 ASSERT( e.what(), "Empty what() string" );
676 ASSERT( __TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(e.what(), EXCEPTION_DESCR2) == 0, "Unknown exception" );
677 ASSERT ( !rg.is_canceling(), "wait() has not reset cancellation state" );
678 exceptionCaught = true;
679 } CATCH_ANY();
680 ASSERT( exceptionCaught, "No exception thrown from the root task group" );
681 ASSERT( g_ExceptionCount >= SKIP_GROUPS, "Too few exceptions from the child groups. The test is broken" );
682 ASSERT( g_ExceptionCount <= NUM_GROUPS - SKIP_GROUPS, "Too many exceptions from the child groups. The test is broken" );
683 ASSERT( g_ExceptionCount < NUM_GROUPS - SKIP_GROUPS, "None of the child groups was cancelled" );
684}
685#endif /* TBB_USE_EXCEPTIONS */
686
687//------------------------------------------------------------------------
688// Tests for manual cancellation of the task_group hierarchy
689//------------------------------------------------------------------------
690
691void TestCancellation1 () {
692 ResetGlobals( false, false );
693 Concurrency::task_group rg;
694 for( unsigned i = 0; i < NUM_GROUPS; ++i )
695 // TBB version does not require taking function address
696 rg.run( &LaunchChildren );
697 ASSERT ( !Concurrency::is_current_task_group_canceling(), "Unexpected cancellation" );
698 ASSERT ( !rg.is_canceling(), "Unexpected cancellation" );
699#if __TBB_SILENT_CANCELLATION_BROKEN
700 g_CancellationPropagationInProgress = true;
701#endif
702 while ( g_MaxConcurrency > 1 && g_TaskCount == 0 )
703 __TBB_Yield();
704 rg.cancel();
705 g_ExecutedAtCancellation = g_TaskCount;
706 ASSERT ( rg.is_canceling(), "No cancellation reported" );
707 rg.wait();
708 ASSERT( g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken" );
709 ASSERT( g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?" );
710 ASSERT( g_TaskCount <= g_ExecutedAtCancellation + Harness::ConcurrencyTracker::PeakParallelism(), "Too many tasks survived cancellation" );
711}
712
713//------------------------------------------------------------------------
714// Tests for manual cancellation of the structured_task_group hierarchy
715//------------------------------------------------------------------------
716
717void StructuredLaunchChildren () {
718 atomic_t count;
719 count = 0;
720 Concurrency::structured_task_group g;
721 bool exceptionCaught = false;
722 typedef Concurrency::task_handle<ThrowingTask> throwing_handle_type;
723 tbb::aligned_space<throwing_handle_type,NUM_CHORES> handles;
724 for( unsigned i = 0; i < NUM_CHORES; ++i ) {
725 throwing_handle_type *h = handles.begin()+i;
726 new ( h ) throwing_handle_type( ThrowingTask(count) );
727 g.run( *h );
728 }
729 __TBB_TRY {
730 g.wait();
731 } __TBB_CATCH( TestException& e ) {
732#if TBB_USE_EXCEPTIONS
733 ASSERT( e.what(), "Empty what() string" );
734 ASSERT( __TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(e.what(), EXCEPTION_DESCR1) == 0, "Unknown exception" );
735#endif /* TBB_USE_EXCEPTIONS */
736#if __TBB_SILENT_CANCELLATION_BROKEN
737 ASSERT ( !g.is_canceling() || g_CancellationPropagationInProgress, "wait() has not reset cancellation state" );
738#else
739 ASSERT ( !g.is_canceling(), "wait() has not reset cancellation state" );
740#endif
741 exceptionCaught = true;
742 ++g_ExceptionCount;
743 } CATCH_ANY();
744 ASSERT( !g_Throw || exceptionCaught, "No exception in the child task group" );
745 for( unsigned i = 0; i < NUM_CHORES; ++i )
746 (handles.begin()+i)->~throwing_handle_type();
747 if ( g_Rethrow && g_ExceptionCount > SKIP_GROUPS ) {
748#if __TBB_SILENT_CANCELLATION_BROKEN
749 g_CancellationPropagationInProgress = true;
750#endif
751 __TBB_THROW( test_exception(EXCEPTION_DESCR2) );
752 }
753}
754
755class StructuredCancellationTestDriver {
756 tbb::aligned_space<handle_type,NUM_CHORES> m_handles;
757
758public:
759 void Launch ( Concurrency::structured_task_group& rg ) {
760 ResetGlobals( false, false );
761 for( unsigned i = 0; i < NUM_GROUPS; ++i ) {
762 handle_type *h = m_handles.begin()+i;
763 new ( h ) handle_type( StructuredLaunchChildren );
764 rg.run( *h );
765 }
766 ASSERT ( !Concurrency::is_current_task_group_canceling(), "Unexpected cancellation" );
767 ASSERT ( !rg.is_canceling(), "Unexpected cancellation" );
768#if __TBB_SILENT_CANCELLATION_BROKEN
769 g_CancellationPropagationInProgress = true;
770#endif
771 while ( g_MaxConcurrency > 1 && g_TaskCount == 0 )
772 __TBB_Yield();
773 }
774
775 void Finish () {
776 for( unsigned i = 0; i < NUM_GROUPS; ++i )
777 (m_handles.begin()+i)->~handle_type();
778 ASSERT( g_TaskCount <= NUM_GROUPS * NUM_CHORES, "Too many tasks reported. The test is broken" );
779 ASSERT( g_TaskCount < NUM_GROUPS * NUM_CHORES, "No tasks were cancelled. Cancellation model changed?" );
780 ASSERT( g_TaskCount <= g_ExecutedAtCancellation + g_MaxConcurrency, "Too many tasks survived cancellation" );
781 }
782}; // StructuredCancellationTestDriver
783
784void TestStructuredCancellation1 () {
785 StructuredCancellationTestDriver driver;
786 Concurrency::structured_task_group sg;
787 driver.Launch( sg );
788 sg.cancel();
789 g_ExecutedAtCancellation = g_TaskCount;
790 ASSERT ( sg.is_canceling(), "No cancellation reported" );
791 sg.wait();
792 driver.Finish();
793}
794
795#if TBB_USE_EXCEPTIONS
796#if defined(_MSC_VER)
797 #pragma warning (disable: 4127)
798#endif
799
800template<bool Throw>
801void TestStructuredCancellation2 () {
802 bool exception_occurred = false,
803 unexpected_exception = false;
804 StructuredCancellationTestDriver driver;
805 try {
806 Concurrency::structured_task_group tg;
807 driver.Launch( tg );
808 if ( Throw )
809 throw int(); // Initiate stack unwinding
810 }
811 catch ( const Concurrency::missing_wait& e ) {
812 ASSERT( e.what(), "Error message is absent" );
813 exception_occurred = true;
814 unexpected_exception = Throw;
815 }
816 catch ( int ) {
817 exception_occurred = true;
818 unexpected_exception = !Throw;
819 }
820 catch ( ... ) {
821 exception_occurred = unexpected_exception = true;
822 }
823 ASSERT( exception_occurred, NULL );
824 ASSERT( !unexpected_exception, NULL );
825 driver.Finish();
826}
827#endif /* TBB_USE_EXCEPTIONS */
828
829void EmptyFunction () {}
830
831void TestStructuredWait () {
832 Concurrency::structured_task_group sg;
833 handle_type h(EmptyFunction);
834 sg.run(h);
835 sg.wait();
836 handle_type h2(EmptyFunction);
837 sg.run(h2);
838 sg.wait();
839}
840
841struct TestFunctor {
842 void operator()() { ASSERT( false, "Non-const operator called" ); }
843 void operator()() const { /* library requires this overload only */ }
844};
845
846void TestConstantFunctorRequirement() {
847 tbb::task_group g;
848 TestFunctor tf;
849 g.run( tf ); g.wait();
850 g.run_and_wait( tf );
851}
852//------------------------------------------------------------------------
853#if __TBB_CPP11_RVALUE_REF_PRESENT
854namespace TestMoveSemanticsNS {
855 struct TestFunctor {
856 void operator()() const {};
857 };
858
859 struct MoveOnlyFunctor : MoveOnly, TestFunctor {
860 MoveOnlyFunctor() : MoveOnly() {};
861 MoveOnlyFunctor(MoveOnlyFunctor&& other) : MoveOnly(std::move(other)) {};
862 };
863
864 struct MovePreferableFunctor : Movable, TestFunctor {
865 MovePreferableFunctor() : Movable() {};
866 MovePreferableFunctor(MovePreferableFunctor&& other) : Movable(std::move(other)) {};
867 MovePreferableFunctor(const MovePreferableFunctor& other) : Movable(other) {};
868 };
869
870 struct NoMoveNoCopyFunctor : NoCopy, TestFunctor {
871 NoMoveNoCopyFunctor() : NoCopy() {};
872 // mv ctor is not allowed as cp ctor from parent NoCopy
873 private:
874 NoMoveNoCopyFunctor(NoMoveNoCopyFunctor&&);
875 };
876
877 void TestFunctorsWithinTaskHandles() {
878 // working with task_handle rvalues is not supported in task_group
879
880 tbb::task_group tg;
881 MovePreferableFunctor mpf;
882 typedef tbb::task_handle<MoveOnlyFunctor> th_mv_only_type;
883 typedef tbb::task_handle<MovePreferableFunctor> th_mv_pref_type;
884
885 th_mv_only_type th_mv_only = th_mv_only_type(MoveOnlyFunctor());
886 tg.run_and_wait(th_mv_only);
887
888 th_mv_only_type th_mv_only1 = th_mv_only_type(MoveOnlyFunctor());
889 tg.run(th_mv_only1);
890 tg.wait();
891
892 th_mv_pref_type th_mv_pref = th_mv_pref_type(mpf);
893 tg.run_and_wait(th_mv_pref);
894 ASSERT(mpf.alive, "object was moved when was passed by lval");
895 mpf.Reset();
896
897 th_mv_pref_type th_mv_pref1 = th_mv_pref_type(std::move(mpf));
898 tg.run_and_wait(th_mv_pref1);
899 ASSERT(!mpf.alive, "object was copied when was passed by rval");
900 mpf.Reset();
901
902 th_mv_pref_type th_mv_pref2 = th_mv_pref_type(mpf);
903 tg.run(th_mv_pref2);
904 tg.wait();
905 ASSERT(mpf.alive, "object was moved when was passed by lval");
906 mpf.Reset();
907
908 th_mv_pref_type th_mv_pref3 = th_mv_pref_type(std::move(mpf));
909 tg.run(th_mv_pref3);
910 tg.wait();
911 ASSERT(!mpf.alive, "object was copied when was passed by rval");
912 mpf.Reset();
913 }
914
915 void TestBareFunctors() {
916 tbb::task_group tg;
917 MovePreferableFunctor mpf;
918 // run_and_wait() doesn't have any copies or moves of arguments inside the impl
919 tg.run_and_wait( NoMoveNoCopyFunctor() );
920
921 tg.run( MoveOnlyFunctor() );
922 tg.wait();
923
924 tg.run( mpf );
925 tg.wait();
926 ASSERT(mpf.alive, "object was moved when was passed by lval");
927 mpf.Reset();
928
929 tg.run( std::move(mpf) );
930 tg.wait();
931 ASSERT(!mpf.alive, "object was copied when was passed by rval");
932 mpf.Reset();
933 }
934
935 void TestMakeTask() {
936 MovePreferableFunctor mpf;
937
938 tbb::make_task( MoveOnly() );
939
940 tbb::make_task( mpf );
941 ASSERT(mpf.alive, "object was moved when was passed by lval");
942 mpf.Reset();
943
944 tbb::make_task( std::move(mpf) );
945 ASSERT(!mpf.alive, "object was copied when was passed by rval");
946 mpf.Reset();
947 }
948}
949#endif /* __TBB_CPP11_RVALUE_REF_PRESENT */
950
951void TestMoveSemantics() {
952#if __TBB_CPP11_RVALUE_REF_PRESENT
953 TestMoveSemanticsNS::TestBareFunctors();
954 TestMoveSemanticsNS::TestFunctorsWithinTaskHandles();
955 TestMoveSemanticsNS::TestMakeTask();
956#else
957 REPORT("Known issue: move support tests are skipped.\n");
958#endif
959}
960//------------------------------------------------------------------------
961
962
963int TestMain () {
964 REMARK ("Testing %s task_group functionality\n", TBBTEST_USE_TBB ? "TBB" : "PPL");
965 for( int p=MinThread; p<=MaxThread; ++p ) {
966 g_MaxConcurrency = p;
967#if TBBTEST_USE_TBB
968 tbb::task_scheduler_init init(p);
969#else
970 Concurrency::SchedulerPolicy sp( 4,
971 Concurrency::SchedulerKind, Concurrency::ThreadScheduler,
972 Concurrency::MinConcurrency, 1,
973 Concurrency::MaxConcurrency, p,
974 Concurrency::TargetOversubscriptionFactor, 1);
975 Concurrency::Scheduler *s = Concurrency::Scheduler::Create( sp );
976#endif /* !TBBTEST_USE_TBB */
977 if ( p > 1 ) {
978 TestParallelSpawn();
979 TestParallelWait();
980 TestVagabondGroup();
981 }
982 TestFib1();
983 TestFib2();
984 TestTaskHandle();
985 TestTaskHandle2<Concurrency::task_group>();
986 TestTaskHandle2<Concurrency::structured_task_group>();
987#if __TBB_CPP11_LAMBDAS_PRESENT
988 TestFibWithLambdas();
989 TestFibWithMakeTask();
990#endif
991 TestCancellation1();
992 TestStructuredCancellation1();
993#if TBB_USE_EXCEPTIONS && !__TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN
994 TestEh1();
995 TestEh2();
996 TestStructuredWait();
997 TestStructuredCancellation2<true>();
998#if !(__TBB_THROW_FROM_DTOR_BROKEN || __TBB_STD_UNCAUGHT_EXCEPTION_BROKEN)
999 TestStructuredCancellation2<false>();
1000#else
1001 REPORT("Known issue: TestStructuredCancellation2<false>() is skipped.\n");
1002#endif
1003#endif /* TBB_USE_EXCEPTIONS && !__TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN */
1004#if !TBBTEST_USE_TBB
1005 s->Release();
1006#endif
1007 }
1008 TestConstantFunctorRequirement();
1009#if __TBB_THROW_ACROSS_MODULE_BOUNDARY_BROKEN
1010 REPORT("Known issue: exception handling tests are skipped.\n");
1011#endif
1012 TestMoveSemantics();
1013 return Harness::Done;
1014}
1015
1016#else /* !__TBB_TASK_GROUP_CONTEXT */
1017
1018#include "harness.h"
1019
1020int TestMain () {
1021 return Harness::Skipped;
1022}
1023
1024#endif /* !__TBB_TASK_GROUP_CONTEXT */
1025