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_task.h"
18#include "tbb/atomic.h"
19#include "tbb/tbb_thread.h"
20#include "tbb/task_scheduler_init.h"
21#include <cstdlib>
22
23//------------------------------------------------------------------------
24// Test for task::spawn_children and task_list
25//------------------------------------------------------------------------
26
27class UnboundedlyRecursiveOnUnboundedStealingTask : public tbb::task {
28 typedef UnboundedlyRecursiveOnUnboundedStealingTask this_type;
29
30 this_type *m_Parent;
31 const int m_Depth;
32 volatile bool m_GoAhead;
33
34 // Well, virtually unboundedly, for any practical purpose
35 static const int max_depth = 1000000;
36
37public:
38 UnboundedlyRecursiveOnUnboundedStealingTask( this_type *parent_ = NULL, int depth_ = max_depth )
39 : m_Parent(parent_)
40 , m_Depth(depth_)
41 , m_GoAhead(true)
42 {}
43
44 tbb::task* execute() __TBB_override {
45 // Using large padding array speeds up reaching stealing limit
46 const int paddingSize = 16 * 1024;
47 volatile char padding[paddingSize];
48 if( !m_Parent || (m_Depth > 0 && m_Parent->m_GoAhead) ) {
49 if ( m_Parent ) {
50 // We are stolen, let our parent start waiting for us
51 m_Parent->m_GoAhead = false;
52 }
53 tbb::task &t = *new( allocate_child() ) this_type(this, m_Depth - 1);
54 set_ref_count( 2 );
55 spawn( t );
56 // Give a willing thief a chance to steal
57 for( int i = 0; i < 1000000 && m_GoAhead; ++i ) {
58 ++padding[i % paddingSize];
59 __TBB_Yield();
60 }
61 // If our child has not been stolen yet, then prohibit it siring ones
62 // of its own (when this thread executes it inside the next wait_for_all)
63 m_GoAhead = false;
64 wait_for_all();
65 }
66 return NULL;
67 }
68}; // UnboundedlyRecursiveOnUnboundedStealingTask
69
70tbb::atomic<int> Count;
71
72class RecursiveTask: public tbb::task {
73 const int m_ChildCount;
74 const int m_Depth;
75 //! Spawn tasks in list. Exact method depends upon m_Depth&bit_mask.
76 void SpawnList( tbb::task_list& list, int bit_mask ) {
77 if( m_Depth&bit_mask ) {
78 // Take address to check that signature of spawn(task_list&) is static.
79 void (*s)(tbb::task_list&) = &tbb::task::spawn;
80 (*s)(list);
81 ASSERT( list.empty(), NULL );
82 wait_for_all();
83 } else {
84 spawn_and_wait_for_all(list);
85 ASSERT( list.empty(), NULL );
86 }
87 }
88public:
89 RecursiveTask( int child_count, int depth_ ) : m_ChildCount(child_count), m_Depth(depth_) {}
90 tbb::task* execute() __TBB_override {
91 ++Count;
92 if( m_Depth>0 ) {
93 tbb::task_list list;
94 ASSERT( list.empty(), NULL );
95 for( int k=0; k<m_ChildCount; ++k ) {
96 list.push_back( *new( allocate_child() ) RecursiveTask(m_ChildCount/2,m_Depth-1 ) );
97 ASSERT( !list.empty(), NULL );
98 }
99 set_ref_count( m_ChildCount+1 );
100 SpawnList( list, 1 );
101 // Now try reusing this as the parent.
102 set_ref_count(2);
103 list.push_back( *new ( allocate_child() ) tbb::empty_task() );
104 SpawnList( list, 2 );
105 }
106 return NULL;
107 }
108};
109
110//! Compute what Count should be after RecursiveTask(child_count,depth) runs.
111static int Expected( int child_count, int depth ) {
112 return depth<=0 ? 1 : 1+child_count*Expected(child_count/2,depth-1);
113}
114
115void TestStealLimit( int nthread ) {
116#if __TBB_DEFINE_MIC
117 REMARK( "skipping steal limiting heuristics for %d threads\n", nthread );
118#else// !_TBB_DEFINE_MIC
119 REMARK( "testing steal limiting heuristics for %d threads\n", nthread );
120 tbb::task_scheduler_init init(nthread);
121 tbb::task &t = *new( tbb::task::allocate_root() ) UnboundedlyRecursiveOnUnboundedStealingTask();
122 tbb::task::spawn_root_and_wait(t);
123#endif// _TBB_DEFINE_MIC
124}
125
126//! Test task::spawn( task_list& )
127void TestSpawnChildren( int nthread ) {
128 REMARK("testing task::spawn(task_list&) for %d threads\n",nthread);
129 tbb::task_scheduler_init init(nthread);
130 for( int j=0; j<50; ++j ) {
131 Count = 0;
132 RecursiveTask& p = *new( tbb::task::allocate_root() ) RecursiveTask(j,4);
133 tbb::task::spawn_root_and_wait(p);
134 int expected = Expected(j,4);
135 ASSERT( Count==expected, NULL );
136 }
137}
138
139//! Test task::spawn_root_and_wait( task_list& )
140void TestSpawnRootList( int nthread ) {
141 REMARK("testing task::spawn_root_and_wait(task_list&) for %d threads\n",nthread);
142 tbb::task_scheduler_init init(nthread);
143 for( int j=0; j<5; ++j )
144 for( int k=0; k<10; ++k ) {
145 Count = 0;
146 tbb::task_list list;
147 for( int i=0; i<k; ++i )
148 list.push_back( *new( tbb::task::allocate_root() ) RecursiveTask(j,4) );
149 tbb::task::spawn_root_and_wait(list);
150 int expected = k*Expected(j,4);
151 ASSERT( Count==expected, NULL );
152 }
153}
154
155//------------------------------------------------------------------------
156// Test for task::recycle_as_safe_continuation
157//------------------------------------------------------------------------
158
159void TestSafeContinuation( int nthread ) {
160 REMARK("testing task::recycle_as_safe_continuation for %d threads\n",nthread);
161 tbb::task_scheduler_init init(nthread);
162 for( int j=8; j<33; ++j ) {
163 TaskGenerator& p = *new( tbb::task::allocate_root() ) TaskGenerator(j,5);
164 tbb::task::spawn_root_and_wait(p);
165 }
166}
167
168//------------------------------------------------------------------------
169// Test affinity interface
170//------------------------------------------------------------------------
171tbb::atomic<int> TotalCount;
172
173struct AffinityTask: public tbb::task {
174 const affinity_id expected_affinity_id;
175 bool noted;
176 /** Computing affinities is NOT supported by TBB, and may disappear in the future.
177 It is done here for sake of unit testing. */
178 AffinityTask( int expected_affinity_id_ ) :
179 expected_affinity_id(affinity_id(expected_affinity_id_)),
180 noted(false)
181 {
182 set_affinity(expected_affinity_id);
183 ASSERT( 0u-expected_affinity_id>0u, "affinity_id not an unsigned integral type?" );
184 ASSERT( affinity()==expected_affinity_id, NULL );
185 }
186 tbb::task* execute() __TBB_override {
187 ++TotalCount;
188 return NULL;
189 }
190 void note_affinity( affinity_id id ) __TBB_override {
191 // There is no guarantee in TBB that a task runs on its affinity thread.
192 // However, the current implementation does accidentally guarantee it
193 // under certain conditions, such as the conditions here.
194 // We exploit those conditions for sake of unit testing.
195 ASSERT( id!=expected_affinity_id, NULL );
196 ASSERT( !noted, "note_affinity_id called twice!" );
197 ASSERT ( &self() == (tbb::task*)this, "Wrong innermost running task" );
198 noted = true;
199 }
200};
201
202/** Note: This test assumes a lot about the internal implementation of affinity.
203 Do NOT use this as an example of good programming practice with TBB */
204void TestAffinity( int nthread ) {
205 TotalCount = 0;
206 int n = tbb::task_scheduler_init::default_num_threads();
207 if( n>nthread )
208 n = nthread;
209 tbb::task_scheduler_init init(n);
210 tbb::empty_task* t = new( tbb::task::allocate_root() ) tbb::empty_task;
211 tbb::task::affinity_id affinity_id = t->affinity();
212 ASSERT( affinity_id==0, NULL );
213 // Set ref_count for n-1 children, plus 1 for the wait.
214 t->set_ref_count(n);
215 // Spawn n-1 affinitized children.
216 for( int i=1; i<n; ++i )
217 tbb::task::spawn( *new(t->allocate_child()) AffinityTask(i) );
218 if( n>1 ) {
219 // Keep master from stealing
220 while( TotalCount!=n-1 )
221 __TBB_Yield();
222 }
223 // Wait for the children
224 t->wait_for_all();
225 int k = 0;
226 GetTaskPtr(k)->destroy(*t);
227 ASSERT(k==1,NULL);
228}
229
230struct NoteAffinityTask: public tbb::task {
231 bool noted;
232 NoteAffinityTask( int id ) : noted(false)
233 {
234 set_affinity(affinity_id(id));
235 }
236 ~NoteAffinityTask () {
237 ASSERT (noted, "note_affinity has not been called");
238 }
239 tbb::task* execute() __TBB_override {
240 return NULL;
241 }
242 void note_affinity( affinity_id /*id*/ ) __TBB_override {
243 noted = true;
244 ASSERT ( &self() == (tbb::task*)this, "Wrong innermost running task" );
245 }
246};
247
248// This test checks one of the paths inside the scheduler by affinitizing the child task
249// to non-existent thread so that it is proxied in the local task pool but not retrieved
250// by another thread.
251// If no workers requested, the extra slot #2 is allocated for a worker thread to serve
252// "enqueued" tasks. In this test, it is used only for the affinity purpose.
253void TestNoteAffinityContext() {
254 tbb::task_scheduler_init init(1);
255 tbb::empty_task* t = new( tbb::task::allocate_root() ) tbb::empty_task;
256 t->set_ref_count(2);
257 // This master in the absence of workers will have an affinity id of 1.
258 // So use another number to make the task get proxied.
259 tbb::task::spawn( *new(t->allocate_child()) NoteAffinityTask(2) );
260 t->wait_for_all();
261 tbb::task::destroy(*t);
262}
263
264//------------------------------------------------------------------------
265// Test that recovery actions work correctly for task::allocate_* methods
266// when a task's constructor throws an exception.
267//------------------------------------------------------------------------
268
269#if TBB_USE_EXCEPTIONS
270static int TestUnconstructibleTaskCount;
271
272struct ConstructionFailure {
273};
274
275#if __TBB_MSVC_UNREACHABLE_CODE_IGNORED
276 // Suppress pointless "unreachable code" warning.
277 #pragma warning (push)
278 #pragma warning (disable: 4702)
279#endif
280
281//! Task that cannot be constructed.
282template<size_t N>
283struct UnconstructibleTask: public tbb::empty_task {
284 char space[N];
285 UnconstructibleTask() {
286 throw ConstructionFailure();
287 }
288};
289
290#if __TBB_MSVC_UNREACHABLE_CODE_IGNORED
291 #pragma warning (pop)
292#endif
293
294#define TRY_BAD_CONSTRUCTION(x) \
295 { \
296 try { \
297 new(x) UnconstructibleTask<N>; \
298 } catch( const ConstructionFailure& ) { \
299 ASSERT( parent()==original_parent, NULL ); \
300 ASSERT( ref_count()==original_ref_count, "incorrectly changed ref_count" );\
301 ++TestUnconstructibleTaskCount; \
302 } \
303 }
304
305template<size_t N>
306struct RootTaskForTestUnconstructibleTask: public tbb::task {
307 tbb::task* execute() __TBB_override {
308 tbb::task* original_parent = parent();
309 ASSERT( original_parent!=NULL, NULL );
310 int original_ref_count = ref_count();
311 TRY_BAD_CONSTRUCTION( allocate_root() );
312 TRY_BAD_CONSTRUCTION( allocate_child() );
313 TRY_BAD_CONSTRUCTION( allocate_continuation() );
314 TRY_BAD_CONSTRUCTION( allocate_additional_child_of(*this) );
315 return NULL;
316 }
317};
318
319template<size_t N>
320void TestUnconstructibleTask() {
321 TestUnconstructibleTaskCount = 0;
322 tbb::task_scheduler_init init;
323 tbb::task* t = new( tbb::task::allocate_root() ) RootTaskForTestUnconstructibleTask<N>;
324 tbb::task::spawn_root_and_wait(*t);
325 ASSERT( TestUnconstructibleTaskCount==4, NULL );
326}
327#endif /* TBB_USE_EXCEPTIONS */
328
329//------------------------------------------------------------------------
330// Test for alignment problems with task objects.
331//------------------------------------------------------------------------
332
333#if _MSC_VER && !defined(__INTEL_COMPILER)
334 // Workaround for pointless warning "structure was padded due to __declspec(align())
335 #pragma warning (push)
336 #pragma warning (disable: 4324)
337#endif
338
339//! Task with members of type T.
340/** The task recursively creates tasks. */
341template<typename T>
342class TaskWithMember: public tbb::task {
343 T x;
344 T y;
345 unsigned char count;
346 tbb::task* execute() __TBB_override {
347 x = y;
348 if( count>0 ) {
349 set_ref_count(2);
350 tbb::task* t = new( allocate_child() ) TaskWithMember<T>(count-1);
351 spawn_and_wait_for_all(*t);
352 }
353 return NULL;
354 }
355public:
356 TaskWithMember( unsigned char n ) : count(n) {}
357};
358
359#if _MSC_VER && !defined(__INTEL_COMPILER)
360 #pragma warning (pop)
361#endif
362
363template<typename T>
364void TestAlignmentOfOneClass() {
365 typedef TaskWithMember<T> task_type;
366 tbb::task* t = new( tbb::task::allocate_root() ) task_type(10);
367 tbb::task::spawn_root_and_wait(*t);
368}
369
370#include "harness_m128.h"
371
372void TestAlignment() {
373 REMARK("testing alignment\n");
374 tbb::task_scheduler_init init;
375 // Try types that have variety of alignments
376 TestAlignmentOfOneClass<char>();
377 TestAlignmentOfOneClass<short>();
378 TestAlignmentOfOneClass<int>();
379 TestAlignmentOfOneClass<long>();
380 TestAlignmentOfOneClass<void*>();
381 TestAlignmentOfOneClass<float>();
382 TestAlignmentOfOneClass<double>();
383#if HAVE_m128
384 TestAlignmentOfOneClass<__m128>();
385#endif
386#if HAVE_m256
387 if (have_AVX()) TestAlignmentOfOneClass<__m256>();
388#endif
389}
390
391//------------------------------------------------------------------------
392// Test for recursing on left while spawning on right
393//------------------------------------------------------------------------
394
395int Fib( int n );
396
397struct RightFibTask: public tbb::task {
398 int* y;
399 const int n;
400 RightFibTask( int* y_, int n_ ) : y(y_), n(n_) {}
401 task* execute() __TBB_override {
402 *y = Fib(n-1);
403 return 0;
404 }
405};
406
407int Fib( int n ) {
408 if( n<2 ) {
409 return n;
410 } else {
411 // y actually does not need to be initialized. It is initialized solely to suppress
412 // a gratuitous warning "potentially uninitialized local variable".
413 int y=-1;
414 tbb::task* root_task = new( tbb::task::allocate_root() ) tbb::empty_task;
415 root_task->set_ref_count(2);
416 tbb::task::spawn( *new( root_task->allocate_child() ) RightFibTask(&y,n) );
417 int x = Fib(n-2);
418 root_task->wait_for_all();
419 tbb::task::destroy(*root_task);
420 return y+x;
421 }
422}
423
424void TestLeftRecursion( int p ) {
425 REMARK("testing non-spawned roots for %d threads\n",p);
426 tbb::task_scheduler_init init(p);
427 int sum = 0;
428 for( int i=0; i<100; ++i )
429 sum +=Fib(10);
430 ASSERT( sum==5500, NULL );
431}
432
433//------------------------------------------------------------------------
434// Test for computing with DAG of tasks.
435//------------------------------------------------------------------------
436
437class DagTask: public tbb::task {
438 typedef unsigned long long number_t;
439 const int i, j;
440 number_t sum_from_left, sum_from_above;
441 void check_sum( number_t sum ) {
442 number_t expected_sum = 1;
443 for( int k=i+1; k<=i+j; ++k )
444 expected_sum *= k;
445 for( int k=1; k<=j; ++k )
446 expected_sum /= k;
447 ASSERT(sum==expected_sum, NULL);
448 }
449public:
450 DagTask *successor_to_below, *successor_to_right;
451 DagTask( int i_, int j_ ) : i(i_), j(j_), sum_from_left(0), sum_from_above(0) {}
452 task* execute() __TBB_override {
453 ASSERT( ref_count()==0, NULL );
454 number_t sum = i==0 && j==0 ? 1 : sum_from_left+sum_from_above;
455 check_sum(sum);
456 ++execution_count;
457 if( DagTask* t = successor_to_right ) {
458 t->sum_from_left = sum;
459 if( t->decrement_ref_count()==0 )
460 // Test using spawn to evaluate DAG
461 spawn( *t );
462 }
463 if( DagTask* t = successor_to_below ) {
464 t->sum_from_above = sum;
465 if( t->add_ref_count(-1)==0 )
466 // Test using bypass to evaluate DAG
467 return t;
468 }
469 return NULL;
470 }
471 ~DagTask() {++destruction_count;}
472 static tbb::atomic<int> execution_count;
473 static tbb::atomic<int> destruction_count;
474};
475
476tbb::atomic<int> DagTask::execution_count;
477tbb::atomic<int> DagTask::destruction_count;
478
479void TestDag( int p ) {
480 REMARK("testing evaluation of DAG for %d threads\n",p);
481 tbb::task_scheduler_init init(p);
482 DagTask::execution_count=0;
483 DagTask::destruction_count=0;
484 const int n = 10;
485 DagTask* a[n][n];
486 for( int i=0; i<n; ++i )
487 for( int j=0; j<n; ++j )
488 a[i][j] = new( tbb::task::allocate_root() ) DagTask(i,j);
489 for( int i=0; i<n; ++i )
490 for( int j=0; j<n; ++j ) {
491 a[i][j]->successor_to_below = i+1<n ? a[i+1][j] : NULL;
492 a[i][j]->successor_to_right = j+1<n ? a[i][j+1] : NULL;
493 a[i][j]->set_ref_count((i>0)+(j>0));
494 }
495 a[n-1][n-1]->increment_ref_count();
496 a[n-1][n-1]->spawn_and_wait_for_all(*a[0][0]);
497 ASSERT( DagTask::execution_count == n*n - 1, NULL );
498 tbb::task::destroy(*a[n-1][n-1]);
499 ASSERT( DagTask::destruction_count > n*n - p, NULL );
500 while ( DagTask::destruction_count != n*n )
501 __TBB_Yield();
502}
503
504#include "harness_barrier.h"
505
506class RelaxedOwnershipTask: public tbb::task {
507 tbb::task &m_taskToSpawn,
508 &m_taskToDestroy,
509 &m_taskToExecute;
510 static Harness::SpinBarrier m_barrier;
511
512 tbb::task* execute () __TBB_override {
513 tbb::task &p = *parent();
514 tbb::task &r = *new( allocate_root() ) tbb::empty_task;
515 r.set_ref_count( 1 );
516 m_barrier.wait();
517 p.spawn( *new(p.allocate_child()) tbb::empty_task );
518 p.spawn( *new(task::allocate_additional_child_of(p)) tbb::empty_task );
519 p.spawn( m_taskToSpawn );
520 p.destroy( m_taskToDestroy );
521 r.spawn_and_wait_for_all( m_taskToExecute );
522 p.destroy( r );
523 return NULL;
524 }
525public:
526 RelaxedOwnershipTask ( tbb::task& toSpawn, tbb::task& toDestroy, tbb::task& toExecute )
527 : m_taskToSpawn(toSpawn)
528 , m_taskToDestroy(toDestroy)
529 , m_taskToExecute(toExecute)
530 {}
531 static void SetBarrier ( int numThreads ) { m_barrier.initialize( numThreads ); }
532};
533
534Harness::SpinBarrier RelaxedOwnershipTask::m_barrier;
535
536void TestRelaxedOwnership( int p ) {
537 if ( p < 2 )
538 return;
539
540 if( unsigned(p)>tbb::tbb_thread::hardware_concurrency() )
541 return;
542
543 REMARK("testing tasks exercising relaxed ownership freedom for %d threads\n", p);
544 tbb::task_scheduler_init init(p);
545 RelaxedOwnershipTask::SetBarrier(p);
546 tbb::task &r = *new( tbb::task::allocate_root() ) tbb::empty_task;
547 tbb::task_list tl;
548 for ( int i = 0; i < p; ++i ) {
549 tbb::task &tS = *new( r.allocate_child() ) tbb::empty_task,
550 &tD = *new( r.allocate_child() ) tbb::empty_task,
551 &tE = *new( r.allocate_child() ) tbb::empty_task;
552 tl.push_back( *new( r.allocate_child() ) RelaxedOwnershipTask(tS, tD, tE) );
553 }
554 r.set_ref_count( 5 * p + 1 );
555 int k=0;
556 GetTaskPtr(k)->spawn( tl );
557 ASSERT(k==1,NULL);
558 r.wait_for_all();
559 r.destroy( r );
560}
561
562//------------------------------------------------------------------------
563// Test for running TBB scheduler on user-created thread.
564//------------------------------------------------------------------------
565
566void RunSchedulerInstanceOnUserThread( int n_child ) {
567 tbb::task* e = new( tbb::task::allocate_root() ) tbb::empty_task;
568 e->set_ref_count(1+n_child);
569 for( int i=0; i<n_child; ++i )
570 tbb::task::spawn( *new(e->allocate_child()) tbb::empty_task );
571 e->wait_for_all();
572 e->destroy(*e);
573}
574
575void TestUserThread( int p ) {
576 tbb::task_scheduler_init init(p);
577 // Try with both 0 and 1 children. Only the latter scenario permits stealing.
578 for( int n_child=0; n_child<2; ++n_child ) {
579 tbb::tbb_thread t( RunSchedulerInstanceOnUserThread, n_child );
580 t.join();
581 }
582}
583
584class TaskWithChildToSteal : public tbb::task {
585 const int m_Depth;
586 volatile bool m_GoAhead;
587
588public:
589 TaskWithChildToSteal( int depth_ )
590 : m_Depth(depth_)
591 , m_GoAhead(false)
592 {}
593
594 tbb::task* execute() __TBB_override {
595 m_GoAhead = true;
596 if ( m_Depth > 0 ) {
597 TaskWithChildToSteal &t = *new( allocate_child() ) TaskWithChildToSteal(m_Depth - 1);
598 t.SpawnAndWaitOnParent();
599 }
600 else
601 Harness::Sleep(50); // The last task in chain sleeps for 50 ms
602 return NULL;
603 }
604
605 void SpawnAndWaitOnParent() {
606 parent()->set_ref_count( 2 );
607 parent()->spawn( *this );
608 while (!this->m_GoAhead )
609 __TBB_Yield();
610 parent()->wait_for_all();
611 }
612}; // TaskWithChildToSteal
613
614// Success criterion of this test is not hanging
615void TestDispatchLoopResponsiveness() {
616 REMARK("testing that dispatch loops do not go into eternal sleep when all remaining children are stolen\n");
617 // Recursion depth values test the following sorts of dispatch loops
618 // 0 - master's outermost
619 // 1 - worker's nested
620 // 2 - master's nested
621 tbb::task_scheduler_init init(2);
622 tbb::task &r = *new( tbb::task::allocate_root() ) tbb::empty_task;
623 for ( int depth = 0; depth < 3; ++depth ) {
624 TaskWithChildToSteal &t = *new( r.allocate_child() ) TaskWithChildToSteal(depth);
625 t.SpawnAndWaitOnParent();
626 }
627 r.destroy(r);
628}
629
630void TestWaitDiscriminativenessWithoutStealing() {
631 REMARK( "testing that task::wait_for_all is specific to the root it is called on (no workers)\n" );
632 // The test relies on the strict LIFO scheduling order in the absence of workers
633 tbb::task_scheduler_init init(1);
634 tbb::task &r1 = *new( tbb::task::allocate_root() ) tbb::empty_task;
635 tbb::task &r2 = *new( tbb::task::allocate_root() ) tbb::empty_task;
636 const int NumChildren = 10;
637 r1.set_ref_count( NumChildren + 1 );
638 r2.set_ref_count( NumChildren + 1 );
639 for( int i=0; i < NumChildren; ++i ) {
640 tbb::empty_task &t1 = *new( r1.allocate_child() ) tbb::empty_task;
641 tbb::empty_task &t2 = *new( r2.allocate_child() ) tbb::empty_task;
642 tbb::task::spawn(t1);
643 tbb::task::spawn(t2);
644 }
645 r2.wait_for_all();
646 ASSERT( r2.ref_count() <= 1, "Not all children of r2 executed" );
647 ASSERT( r1.ref_count() > 1, "All children of r1 prematurely executed" );
648 r1.wait_for_all();
649 ASSERT( r1.ref_count() <= 1, "Not all children of r1 executed" );
650 r1.destroy(r1);
651 r2.destroy(r2);
652}
653
654
655using tbb::internal::spin_wait_until_eq;
656
657//! Deterministic emulation of a long running task
658class LongRunningTask : public tbb::task {
659 volatile bool& m_CanProceed;
660
661 tbb::task* execute() __TBB_override {
662 spin_wait_until_eq( m_CanProceed, true );
663 return NULL;
664 }
665public:
666 LongRunningTask ( volatile bool& canProceed ) : m_CanProceed(canProceed) {}
667};
668
669void TestWaitDiscriminativenessWithStealing() {
670 if( tbb::tbb_thread::hardware_concurrency() < 2 )
671 return;
672 REMARK( "testing that task::wait_for_all is specific to the root it is called on (one worker)\n" );
673 volatile bool canProceed = false;
674 tbb::task_scheduler_init init(2);
675 tbb::task &r1 = *new( tbb::task::allocate_root() ) tbb::empty_task;
676 tbb::task &r2 = *new( tbb::task::allocate_root() ) tbb::empty_task;
677 r1.set_ref_count( 2 );
678 r2.set_ref_count( 2 );
679 tbb::task& t1 = *new( r1.allocate_child() ) tbb::empty_task;
680 tbb::task& t2 = *new( r2.allocate_child() ) LongRunningTask(canProceed);
681 tbb::task::spawn(t2);
682 tbb::task::spawn(t1);
683 r1.wait_for_all();
684 ASSERT( r1.ref_count() <= 1, "Not all children of r1 executed" );
685 ASSERT( r2.ref_count() == 2, "All children of r2 prematurely executed" );
686 canProceed = true;
687 r2.wait_for_all();
688 ASSERT( r2.ref_count() <= 1, "Not all children of r2 executed" );
689 r1.destroy(r1);
690 r2.destroy(r2);
691}
692
693struct MasterBody : NoAssign, Harness::NoAfterlife {
694 static Harness::SpinBarrier my_barrier;
695
696 class BarrenButLongTask : public tbb::task {
697 volatile bool& m_Started;
698 volatile bool& m_CanProceed;
699
700 tbb::task* execute() __TBB_override {
701 m_Started = true;
702 spin_wait_until_eq( m_CanProceed, true );
703 volatile int k = 0;
704 for ( int i = 0; i < 1000000; ++i ) ++k;
705 return NULL;
706 }
707 public:
708 BarrenButLongTask ( volatile bool& started, volatile bool& can_proceed )
709 : m_Started(started), m_CanProceed(can_proceed)
710 {}
711 };
712
713 class BinaryRecursiveTask : public tbb::task {
714 int m_Depth;
715
716 tbb::task* execute() __TBB_override {
717 if( !m_Depth )
718 return NULL;
719 set_ref_count(3);
720 spawn( *new( allocate_child() ) BinaryRecursiveTask(m_Depth - 1) );
721 spawn( *new( allocate_child() ) BinaryRecursiveTask(m_Depth - 1) );
722 wait_for_all();
723 return NULL;
724 }
725
726 void note_affinity( affinity_id ) __TBB_override {
727 ASSERT( false, "These tasks cannot be stolen" );
728 }
729 public:
730 BinaryRecursiveTask ( int depth_ ) : m_Depth(depth_) {}
731 };
732
733 void operator() ( int id ) const {
734 if ( id ) {
735 tbb::task_scheduler_init init(2);
736 volatile bool child_started = false,
737 can_proceed = false;
738 tbb::task& r = *new( tbb::task::allocate_root() ) tbb::empty_task;
739 r.set_ref_count(2);
740 r.spawn( *new(r.allocate_child()) BarrenButLongTask(child_started, can_proceed) );
741 spin_wait_until_eq( child_started, true );
742 my_barrier.wait();
743 can_proceed = true;
744 r.wait_for_all();
745 r.destroy(r);
746 }
747 else {
748 my_barrier.wait();
749 tbb::task_scheduler_init init(1);
750 Count = 0;
751 int depth = 16;
752 BinaryRecursiveTask& r = *new( tbb::task::allocate_root() ) BinaryRecursiveTask(depth);
753 tbb::task::spawn_root_and_wait(r);
754 }
755 }
756public:
757 MasterBody ( int num_masters ) { my_barrier.initialize(num_masters); }
758};
759
760Harness::SpinBarrier MasterBody::my_barrier;
761
762/** Ensures that tasks spawned by a master thread or one of the workers servicing
763 it cannot be stolen by another master thread. **/
764void TestMastersIsolation ( int p ) {
765 // The test requires at least 3-way parallelism to work correctly
766 if ( p > 2 && tbb::task_scheduler_init::default_num_threads() >= p ) {
767 tbb::task_scheduler_init init(p);
768 NativeParallelFor( p, MasterBody(p) );
769 }
770}
771
772struct waitable_task : tbb::task {
773 tbb::task* execute() __TBB_override {
774 recycle_as_safe_continuation(); // do not destroy the task after execution
775 set_parent(this); // decrement its own ref_count after completion
776 __TBB_Yield();
777 return NULL;
778 }
779};
780void TestWaitableTask() {
781 waitable_task &wt = *new( tbb::task::allocate_root() ) waitable_task;
782 for( int i = 0; i < 100000; i++ ) {
783 wt.set_ref_count(2); // prepare for waiting on it
784 wt.spawn(wt);
785 if( i&1 ) __TBB_Yield();
786 wt.wait_for_all();
787 }
788 wt.set_parent(NULL); // prevents assertions and atomics in task::destroy
789 tbb::task::destroy(wt);
790}
791
792#if __TBB_PREVIEW_CRITICAL_TASKS
793#include <stdexcept>
794#include <vector>
795#include <map>
796#include "tbb/parallel_for.h"
797
798namespace CriticalTaskSupport {
799
800using tbb::task;
801task* g_root_task = NULL;
802
803// markers to capture execution profile (declaration order is important)
804enum task_marker_t {
805 no_task, regular_task, isolated_regular_task,
806 outer_critical_task, nested_critical_task, critical_from_isolated_task, bypassed_critical_task
807};
808enum bypassed_critical_task_stage_t { not_bypassed, bypassed, executed };
809
810typedef std::vector< std::vector<task_marker_t> > task_map_t;
811task_map_t g_execution_profile;
812
813const int g_per_thread_regular_tasks_num = 5;
814const int g_isolated_regular_task_num = 3;
815tbb::atomic<bool> g_is_critical_task_submitted;
816size_t g_bypassed_critical_task_index = size_t(-1);
817task* g_bypassed_task_pointer = NULL;
818int g_bypassed_task_creator = -1;
819tbb::atomic<bypassed_critical_task_stage_t> g_bypassed_critical_task_stage;
820tbb::task_arena g_arena;
821Harness::SpinBarrier g_spin_barrier;
822
823struct parallel_for_body {
824 parallel_for_body(task_marker_t task_marker, bool submit_critical = false)
825 : my_task_marker(task_marker), my_submit_critical(submit_critical) {}
826 void operator()( int i ) const;
827private:
828 task_marker_t my_task_marker;
829 bool my_submit_critical;
830};
831
832struct IsolatedFunctor {
833 void operator()() const {
834 parallel_for_body body(isolated_regular_task, /*submit_critical=*/ true);
835 tbb::parallel_for( 0, g_isolated_regular_task_num, body, tbb::simple_partitioner() );
836 }
837};
838
839struct CriticalTaskBody : public task {
840 CriticalTaskBody(task_marker_t task_marker) : my_task_mark(task_marker) {}
841 task* execute() __TBB_override {
842 task* ret_task = NULL;
843 task* nested_task = NULL;
844 int thread_idx = tbb::this_task_arena::current_thread_index();
845 g_execution_profile[thread_idx].push_back(my_task_mark);
846 switch( my_task_mark ) {
847 case outer_critical_task:
848 g_spin_barrier.wait(); // allow each thread to take its own critical task
849 // prefill queue with critical tasks
850 nested_task = new( task::allocate_additional_child_of(*g_root_task) )
851 CriticalTaskBody(nested_critical_task);
852 enqueue( *nested_task, tbb::priority_t(tbb::internal::priority_critical) );
853 if( not_bypassed ==
854 g_bypassed_critical_task_stage.compare_and_swap(bypassed, not_bypassed) ) {
855
856 // first, should process all the work from isolated region
857 tbb::this_task_arena::isolate( IsolatedFunctor() );
858
859 CriticalTaskBody* bypassed_task =
860 new( task::allocate_additional_child_of(*g_root_task) )
861 CriticalTaskBody(bypassed_critical_task);
862 g_bypassed_task_pointer = bypassed_task;
863 g_bypassed_critical_task_index = g_execution_profile[thread_idx].size() + 1;
864 g_bypassed_task_creator = thread_idx;
865 tbb::internal::make_critical(*bypassed_task);
866 ret_task = bypassed_task;
867 }
868 g_spin_barrier.wait(); // allow thread to execute isolated region
869 break;
870 case nested_critical_task:
871 // wait until bypassed critical task has been executed
872 g_spin_barrier.wait();
873 break;
874 case bypassed_critical_task:
875 ASSERT( bypassed == g_bypassed_critical_task_stage, "Unexpected bypassed critical task" );
876 g_bypassed_critical_task_stage = executed;
877 ASSERT( thread_idx == g_bypassed_task_creator,
878 "Bypassed critical task is not being executed by the thread that bypassed it." );
879 ASSERT( g_bypassed_task_pointer == this, "This is not bypassed task." );
880 ASSERT( g_bypassed_critical_task_index == g_execution_profile[thread_idx].size(),
881 "Bypassed critical task was not selected as the next task." );
882 break;
883 case critical_from_isolated_task:
884 break;
885 default:
886 ASSERT( false, "Incorrect critical task id." );
887 }
888 return ret_task;
889 }
890private:
891 task_marker_t my_task_mark;
892};
893
894void parallel_for_body::operator()( int i ) const {
895 int thread_idx = tbb::this_task_arena::current_thread_index();
896 g_execution_profile[thread_idx].push_back(my_task_marker);
897 if( my_submit_critical && i == 0 ) {
898 task* isolated_task = new( task::allocate_additional_child_of(*g_root_task) )
899 CriticalTaskBody(critical_from_isolated_task);
900 task::enqueue( *isolated_task, tbb::priority_t(tbb::internal::priority_critical) );
901 }
902}
903
904struct TaskBody: public task {
905 TaskBody() {}
906 TaskBody(task_marker_t /*mark*/) {}
907 task* execute() __TBB_override {
908 int thread_idx = tbb::this_task_arena::current_thread_index();
909 g_execution_profile[thread_idx].push_back(regular_task);
910 if( !g_is_critical_task_submitted ) {
911 g_spin_barrier.wait(); // allow each thread to take its own task.
912 // prefill task pools with regular tasks
913 int half = g_per_thread_regular_tasks_num / 2;
914 for( int i = 0; i < half; ++i ) {
915 task& t = *new( task::allocate_additional_child_of(*g_root_task) )
916 TaskBody;
917 spawn(t);
918 }
919 {
920 // prefill with critical tasks
921 task& t = *new( task::allocate_additional_child_of(*g_root_task) )
922 CriticalTaskBody(outer_critical_task);
923 tbb::internal::make_critical(t);
924 tbb::task::spawn(t);
925 }
926 // prefill task pools with regular tasks
927 for( int i = half; i < g_per_thread_regular_tasks_num; ++i ) {
928 task& t = *new( task::allocate_additional_child_of(*g_root_task) )
929 TaskBody;
930 spawn(t);
931 }
932 g_is_critical_task_submitted.store<tbb::relaxed>(true);
933 g_spin_barrier.wait();
934 }
935 return NULL;
936 }
937};
938
939template<typename TaskType, void(*submit_task)(task&)>
940struct WorkCreator {
941 WorkCreator(task*& root_task, size_t num_tasks, size_t num_critical_tasks = 0,
942 tbb::task_group_context* ctx = NULL)
943 : my_root_task(root_task), my_num_tasks(num_tasks), my_num_critical_tasks(num_critical_tasks),
944 my_context(ctx) {}
945 void operator()() const {
946 ASSERT( my_root_task == NULL, "Incorrect test set up." );
947 task* root_task = NULL;
948 if( my_context )
949 root_task = new( task::allocate_root(*my_context) ) TaskType(regular_task);
950 else
951 root_task = new( task::allocate_root() ) TaskType(regular_task);
952 root_task->increment_ref_count();
953 for( size_t i = 0; i < my_num_tasks; ++i ) {
954 task& t = *new( task::allocate_additional_child_of(*root_task) ) TaskType(regular_task);
955 submit_task(t);
956 }
957 for( size_t i = 0; i < my_num_critical_tasks; ++i ) {
958 task& t = *new( task::allocate_additional_child_of(*root_task) )
959 TaskType( outer_critical_task );
960 tbb::task::enqueue( t, tbb::priority_t(tbb::internal::priority_critical) );
961 }
962 my_root_task = root_task;
963 }
964private:
965 task*& my_root_task;
966 size_t my_num_tasks;
967 size_t my_num_critical_tasks;
968 tbb::task_group_context* my_context;
969};
970
971struct WorkAwaiter {
972 WorkAwaiter(task*& root_task) : my_root_task(root_task) {}
973 void operator()() const {
974 while( !my_root_task ) __TBB_Yield(); // waiting on a tree construction
975 my_root_task->wait_for_all();
976 task::destroy(*my_root_task);
977 my_root_task = NULL;
978 }
979private:
980 task*& my_root_task;
981};
982
983void TestSchedulerTaskSelectionWhenSpawn() {
984 REMARK( "\tPreferring critical tasks among spawned\n" );
985 typedef std::multimap<task_marker_t, task_marker_t> state_machine_t;
986 typedef state_machine_t::iterator states_it;
987 task_marker_t from_to_pairs[] = {
988 // from regular
989 regular_task, regular_task,
990 regular_task, outer_critical_task,
991 // from outermost critical
992 outer_critical_task, isolated_regular_task,
993 outer_critical_task, critical_from_isolated_task,
994 outer_critical_task, nested_critical_task,
995 // from isolated regular
996 isolated_regular_task, isolated_regular_task,
997 isolated_regular_task, critical_from_isolated_task,
998 isolated_regular_task, bypassed_critical_task,
999 // from critical that was enqueued from isolated region
1000 critical_from_isolated_task, isolated_regular_task,
1001 critical_from_isolated_task, nested_critical_task,
1002 critical_from_isolated_task, regular_task,
1003 critical_from_isolated_task, bypassed_critical_task,
1004 // from bypassed critical
1005 bypassed_critical_task, nested_critical_task,
1006 bypassed_critical_task, critical_from_isolated_task,
1007 // from nested critical
1008 nested_critical_task, critical_from_isolated_task,
1009 nested_critical_task, regular_task
1010 };
1011
1012 state_machine_t allowed_transitions;
1013 for( size_t i = 0; i < sizeof(from_to_pairs) / sizeof(from_to_pairs[0]); i += 2 )
1014 allowed_transitions.insert( std::make_pair( from_to_pairs[i], from_to_pairs[i+1] ) );
1015
1016 for( int num_threads = MinThread; num_threads <= MaxThread; ++num_threads ) {
1017 for( int repeat = 0; repeat < 10; ++repeat ) {
1018 // test initialization
1019 g_bypassed_critical_task_stage = not_bypassed;
1020 g_is_critical_task_submitted = false;
1021 g_bypassed_critical_task_index = size_t(-1);
1022 g_bypassed_task_creator = -1;
1023 g_bypassed_task_pointer = NULL;
1024 g_execution_profile.resize(num_threads);
1025 g_spin_barrier.initialize(num_threads);
1026 g_arena.initialize(num_threads);
1027
1028 // test execution
1029 g_arena.execute(
1030 WorkCreator<TaskBody, task::spawn>(g_root_task, /*num_tasks=*/size_t(num_threads)) );
1031 g_arena.execute( WorkAwaiter(g_root_task) );
1032
1033 // checking how execution went
1034 int critical_task_count = 0;
1035 for( int thread = 0; thread < num_threads; ++thread ) {
1036 bool started_critical_region = false;
1037 bool pass_through_critical_region = false;
1038 size_t thread_task_num = g_execution_profile[thread].size();
1039 for( size_t task_index = 0; task_index < thread_task_num; ++task_index ) {
1040 const task_marker_t& executed_task = g_execution_profile[thread][task_index];
1041
1042 if( pass_through_critical_region ) {
1043 ASSERT( executed_task < outer_critical_task,
1044 "Thread did not process all the critical work at once." );
1045 } else if( isolated_regular_task <= executed_task &&
1046 executed_task <= bypassed_critical_task) {
1047 started_critical_region = true;
1048 if( isolated_regular_task < executed_task )
1049 ++critical_task_count;
1050 if( bypassed_critical_task == executed_task ) {
1051 size_t expected_bypass_task_min_index =
1052 /* number of regular task before critical region */1 +
1053 /* number of outermost critical tasks before isolated region */ 1 +
1054 g_isolated_regular_task_num;
1055 size_t expected_bypass_task_max_index = expected_bypass_task_min_index +
1056 /* number of critical tasks inside isolated region */ 1;
1057 ASSERT( expected_bypass_task_min_index <= task_index &&
1058 task_index <= expected_bypass_task_max_index,
1059 "Bypassed critical task has been executed in wrong order" );
1060 }
1061 } else if( started_critical_region ) {
1062 pass_through_critical_region = true;
1063 started_critical_region = false;
1064 }
1065
1066 if( thread_task_num - 1 == task_index )
1067 continue; // no transition check for the last executed task
1068 const task_marker_t& next_task = g_execution_profile[thread][task_index + 1];
1069 std::pair<states_it, states_it> range =
1070 allowed_transitions.equal_range( executed_task );
1071 bool is_choosen_task_allowed = false;
1072 for (states_it it = range.first; it != range.second; ++it) {
1073 is_choosen_task_allowed |= next_task == it->second;
1074 }
1075 ASSERT( is_choosen_task_allowed, "Thread chose incorrect task for execution." );
1076 }
1077 }
1078 ASSERT( critical_task_count == 2 * num_threads + 2, "Wrong number of critical tasks" );
1079 ASSERT( g_bypassed_critical_task_stage == executed, "Was bypassed critical task executed?" );
1080
1081 // test deinitialization
1082 g_execution_profile.clear();
1083 g_arena.terminate();
1084 }
1085 }
1086}
1087
1088struct TaskTypeExecutionMarker : public task {
1089 TaskTypeExecutionMarker( task_marker_t mark ) : my_mark( mark ) {}
1090 task* execute() __TBB_override {
1091 g_execution_profile[tbb::this_task_arena::current_thread_index()].push_back( my_mark );
1092 return NULL;
1093 }
1094private:
1095 task_marker_t my_mark;
1096};
1097
1098struct RegularTaskMarkChecker {
1099 bool operator()(const task_marker_t& m) { return regular_task == m; }
1100};
1101
1102void TestSchedulerTaskSelectionWhenEnqueue() {
1103 REMARK( "\tPreferring critical tasks among enqueued\n" );
1104 g_execution_profile.clear();
1105 // creating two profiles because of enforced concurrency
1106 g_execution_profile.resize(2);
1107 g_root_task = NULL;
1108 unsigned task_num = 99;
1109 unsigned num_critical_tasks = 1;
1110 g_arena.initialize( /*num_threads=*/1, /*reserved_for_masters=*/0 );
1111 g_arena.enqueue(
1112 WorkCreator<TaskTypeExecutionMarker, task::enqueue>(
1113 g_root_task, task_num, num_critical_tasks)
1114 );
1115 WorkAwaiter awaiter(g_root_task); awaiter(); // waiting outside arena
1116 g_arena.terminate();
1117
1118 unsigned idx = !g_execution_profile[1].empty();
1119 ASSERT( g_execution_profile[!idx].empty(), "" );
1120
1121 ASSERT( g_execution_profile[idx].size() == task_num + num_critical_tasks,
1122 "Incorrect number of tasks executed" );
1123 ASSERT( g_execution_profile[idx][0] == outer_critical_task,
1124 "Critical task was executed in wrong order." );
1125 bool all_regular = true;
1126 for( std::vector<task_marker_t>::const_iterator it = g_execution_profile[idx].begin() + 1;
1127 it != g_execution_profile[idx].end(); ++it )
1128 all_regular &= regular_task == *it;
1129 ASSERT( all_regular, "Critical task was executed in wrong order." );
1130}
1131
1132enum ways_to_cancel_t {
1133 by_explicit_call = 0,
1134 by_exception,
1135 no_cancellation
1136};
1137
1138tbb::atomic<size_t> g_num_executed_from_cancelled_context;
1139tbb::atomic<size_t> g_num_executed_from_working_context;
1140int g_cancelling_task_id = -1;
1141
1142#if _MSC_VER && !__INTEL_COMPILER
1143#pragma warning (push)
1144#pragma warning (disable: 4127) /* suppress conditional expression is constant */
1145#endif
1146
1147template<bool cancelled_group>
1148struct ATask : public task {
1149 ATask( task_marker_t /*mark*/ ) : my_cancellation_method( no_cancellation ) {}
1150 ATask( ways_to_cancel_t cancellation_method ) : my_cancellation_method( cancellation_method ) {}
1151 task* execute() __TBB_override {
1152 while( ! g_is_critical_task_submitted ) __TBB_Yield();
1153 // scheduler should take critical task as the next task for execution.
1154 bypassed_critical_task_stage_t previous_critical_task_stage =
1155 g_bypassed_critical_task_stage.compare_and_swap(bypassed, not_bypassed);
1156 while(
1157 cancelled_group // Only tasks from cancelled group wait
1158 && !this->is_cancelled() // for their group to be cancelled
1159 && !tbb::internal::is_critical(*this) // allowing thread that took critical task
1160 && bypassed == previous_critical_task_stage // to proceed and cancel the whole group.
1161 ) __TBB_Yield();
1162 if( cancelled_group )
1163 ++g_num_executed_from_cancelled_context;
1164 else
1165 ++g_num_executed_from_working_context;
1166 switch( my_cancellation_method ) {
1167 case by_explicit_call:
1168 g_cancelling_task_id = int(g_num_executed_from_cancelled_context);
1169 self().cancel_group_execution();
1170 break;
1171 case by_exception:
1172 g_cancelling_task_id = int(g_num_executed_from_cancelled_context);
1173 throw std::runtime_error("Exception data");
1174 break;
1175 case no_cancellation: break;
1176 default:
1177 ASSERT( false, "Should not be here!" );
1178 break;
1179 }
1180 return NULL;
1181 }
1182private:
1183 ways_to_cancel_t my_cancellation_method;
1184};
1185
1186#if _MSC_VER && !__INTEL_COMPILER
1187#pragma warning (pop)
1188#endif
1189
1190template<void(*submit_task)(task&)>
1191struct SubmitTaskFunctor {
1192 SubmitTaskFunctor( task& t ) : my_task( t ) {}
1193 void operator()() const {
1194 submit_task(my_task);
1195 }
1196private:
1197 task& my_task;
1198};
1199
1200void TestCancellation(bool cancel_by_exception) {
1201 g_is_critical_task_submitted = false;
1202 g_bypassed_critical_task_stage = not_bypassed;
1203 tbb::task_group_context context_to_leave_working;
1204 tbb::task_group_context context_to_cancel;
1205 task* root_task_of_to_be_cancelled_context = NULL;
1206 task* root_task_of_working_to_completion_context = NULL;
1207 size_t task_num = 64;
1208 size_t task_num_for_cancelled_context = 2 * MaxThread;
1209 g_num_executed_from_cancelled_context = g_num_executed_from_working_context = 0;
1210 g_cancelling_task_id = -1;
1211 g_arena.initialize( MaxThread ); // leaving one slot to be occupied by master to submit the work
1212 g_arena.execute(
1213 WorkCreator<ATask</*cancelled_group=*/true>, task::spawn>
1214 (root_task_of_to_be_cancelled_context, task_num_for_cancelled_context,
1215 /*num_critical_tasks=*/0, &context_to_cancel)
1216 );
1217 g_arena.execute(
1218 WorkCreator<ATask</*cancelled_group=*/false>, task::spawn>
1219 (root_task_of_working_to_completion_context, task_num, /*num_critical_tasks=*/1,
1220 &context_to_leave_working)
1221 );
1222 ways_to_cancel_t cancellation_method = ways_to_cancel_t( cancel_by_exception );
1223 task& terminating_task = *new( task::allocate_additional_child_of(*root_task_of_to_be_cancelled_context) )
1224 ATask</*cancelled_group=*/true>( cancellation_method );
1225 tbb::internal::make_critical( terminating_task ); // stop the work as soon as possible!
1226 g_arena.enqueue( SubmitTaskFunctor<task::enqueue>(terminating_task),
1227 tbb::priority_t(tbb::internal::priority_critical) );
1228 g_is_critical_task_submitted = true;
1229 try {
1230 g_arena.execute( WorkAwaiter(root_task_of_to_be_cancelled_context) );
1231 } catch( const std::runtime_error& e ) {
1232 ASSERT( cancel_by_exception, "Exception was not expected!" );
1233 ASSERT( std::string(e.what()) == "Exception data", "Unexpected exception data!" );
1234 } catch( const tbb::captured_exception& e ) {
1235 ASSERT( cancel_by_exception, "Exception was not expected!" );
1236 ASSERT( std::string(e.what()) == "Exception data", "Unexpected exception data!" );
1237 } catch( ... ) {
1238 ASSERT( false, "Failed to catch specific exception" );
1239 }
1240 g_arena.execute( WorkAwaiter(root_task_of_working_to_completion_context) );
1241 g_arena.terminate();
1242
1243 if( !cancel_by_exception ) {
1244 ASSERT( context_to_cancel.is_group_execution_cancelled(), "Execution must be cancelled" );
1245 }
1246 ASSERT( !context_to_leave_working.is_group_execution_cancelled(),
1247 "Execution must NOT be cancelled" );
1248
1249 ASSERT( g_num_executed_from_working_context == task_num + /*one critical*/1,
1250 "Incorrect number of tasks executed!" );
1251 ASSERT( g_num_executed_from_cancelled_context < task_num_for_cancelled_context,
1252 "Number of executed tasks from the cancelled context should be less than submitted!" );
1253 ASSERT( 0 < g_cancelling_task_id && g_cancelling_task_id < MaxThread + 1,
1254 "Critical task was executed in wrong order." );
1255}
1256
1257void TestCancellationSupport(bool cancel_by_exception) {
1258 const char* test_type[] = { "by explicit call to cancel", "by throwing an exception" };
1259 REMARK( "\tCancellation support %s\n", test_type[!!cancel_by_exception] );
1260 TestCancellation( cancel_by_exception );
1261}
1262
1263namespace NestedArenaCase {
1264
1265static const size_t g_num_critical_tasks = 10;
1266static const size_t g_num_critical_nested = 5;
1267
1268struct CriticalTask : public task {
1269 CriticalTask(task_marker_t /*mark*/) {}
1270 task* execute() __TBB_override {
1271 ++g_num_executed_from_working_context;
1272 task* nested_root = NULL;
1273 if( !g_is_critical_task_submitted ) {
1274 g_is_critical_task_submitted = true;
1275 g_arena.execute(
1276 WorkCreator<CriticalTask, task::spawn>(nested_root, /*num_tasks=*/size_t(0),
1277 g_num_critical_nested) );
1278 g_arena.execute( WorkAwaiter(nested_root) );
1279 }
1280 return NULL;
1281 }
1282};
1283
1284void TestInNestedArena(tbb::task_arena& outer_arena) {
1285 g_root_task = NULL;
1286 g_is_critical_task_submitted = false;
1287 g_num_executed_from_working_context = 0;
1288 g_arena.initialize( 1 );
1289 outer_arena.execute(
1290 WorkCreator<CriticalTask, task::spawn>(
1291 g_root_task, /*num_tasks=*/size_t(0), g_num_critical_tasks) );
1292 outer_arena.execute( WorkAwaiter(g_root_task) );
1293 ASSERT( g_num_executed_from_working_context == g_num_critical_tasks + g_num_critical_nested,
1294 "Mismatch in number of critical tasks executed in nested and outer arenas." );
1295 g_arena.terminate();
1296}
1297
1298void test() {
1299 REMARK( "\tWork in nested arenas\n" );
1300 TestInNestedArena( g_arena );
1301
1302 tbb::task_arena a( 1 );
1303 TestInNestedArena( a );
1304}
1305} // namespace NestedArenaCase
1306
1307void test() {
1308 REMARK("Testing support for critical tasks\n");
1309 TestSchedulerTaskSelectionWhenSpawn();
1310 TestSchedulerTaskSelectionWhenEnqueue();
1311 TestCancellationSupport(/*cancel_by_exception=*/false);
1312 TestCancellationSupport(/*cancel_by_exception=*/true);
1313 NestedArenaCase::test();
1314}
1315} // namespace CriticalTaskSupport
1316#endif /* __TBB_PREVIEW_CRITICAL_TASKS */
1317
1318int TestMain () {
1319#if TBB_USE_EXCEPTIONS
1320 TestUnconstructibleTask<1>();
1321 TestUnconstructibleTask<10000>();
1322#endif
1323 TestAlignment();
1324 TestNoteAffinityContext();
1325 TestDispatchLoopResponsiveness();
1326 TestWaitDiscriminativenessWithoutStealing();
1327 TestWaitDiscriminativenessWithStealing();
1328 for( int p=MinThread; p<=MaxThread; ++p ) {
1329 TestSpawnChildren( p );
1330 TestSpawnRootList( p );
1331 TestSafeContinuation( p );
1332 TestLeftRecursion( p );
1333 TestDag( p );
1334 TestAffinity( p );
1335 TestUserThread( p );
1336 TestStealLimit( p );
1337 TestRelaxedOwnership( p );
1338 TestMastersIsolation( p );
1339 }
1340 TestWaitableTask();
1341#if __TBB_PREVIEW_CRITICAL_TASKS
1342 CriticalTaskSupport::test();
1343#endif
1344 return Harness::Done;
1345}
1346