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 | |
76 | unsigned g_MaxConcurrency = 0; |
77 | |
78 | typedef tbb::atomic<uint_t> atomic_t; |
79 | typedef 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 | |
87 | enum SharingMode { |
88 | VagabondGroup = 1, |
89 | ParallelWait = 2 |
90 | }; |
91 | |
92 | class 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 | |
155 | public: |
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 | |
203 | atomic_t SharedGroupBodyImpl::s_tasksExecuted; |
204 | |
205 | class SharedGroupBody : NoAssign, Harness::NoAfterlife { |
206 | bool m_bOwner; |
207 | SharedGroupBodyImpl *m_pImpl; |
208 | public: |
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 | |
226 | class RunAndWaitSyncronizationTestBody : NoAssign { |
227 | Harness::SpinBarrier& m_barrier; |
228 | tbb::atomic<bool>& m_completed; |
229 | tbb::task_group& m_tg; |
230 | public: |
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 | |
251 | void TestParallelSpawn () { |
252 | NativeParallelFor( g_MaxConcurrency, SharedGroupBody(g_MaxConcurrency) ); |
253 | } |
254 | |
255 | void 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) |
267 | void TestVagabondGroup () { |
268 | NativeParallelFor( 2, SharedGroupBody(2, VagabondGroup) ); |
269 | } |
270 | |
271 | //------------------------------------------------------------------------ |
272 | // Common requisites of the Fibonacci tests |
273 | //------------------------------------------------------------------------ |
274 | |
275 | const uint_t N = 20; |
276 | const uint_t F = 6765; |
277 | |
278 | atomic_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 | |
297 | template<uint_t Func(uint_t)> |
298 | struct 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 | |
307 | uint_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 | |
321 | void 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 | |
339 | uint_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 | |
354 | void RunFib2 () { |
355 | g_Sum += Fib_SpawnBothChildren(N); |
356 | } |
357 | |
358 | void 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 | |
376 | class FibTask_SpawnRightChildOnly : NoAssign, Harness::NoAfterlife { |
377 | uint_t* m_pRes; |
378 | mutable uint_t m_Num; |
379 | |
380 | public: |
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 | |
399 | uint_t RunFib3 ( uint_t n ) { |
400 | uint_t res = ~0u; |
401 | FibTask_SpawnRightChildOnly func(&res, n); |
402 | func(); |
403 | return res; |
404 | } |
405 | |
406 | void 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 | |
419 | template<class task_group_type> |
420 | class FibTask_SpawnBothChildren : NoAssign, Harness::NoAfterlife { |
421 | uint_t* m_pRes; |
422 | uint_t m_Num; |
423 | public: |
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 | |
444 | template<class task_group_type> |
445 | void RunFib4 () { |
446 | uint_t res = ~0u; |
447 | FibTask_SpawnBothChildren<task_group_type> func(&res, N); |
448 | func(); |
449 | g_Sum += res; |
450 | } |
451 | |
452 | template<class task_group_type> |
453 | void 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 | |
509 | void 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 | |
526 | void 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 | |
544 | class test_exception : public std::exception |
545 | { |
546 | const char* m_strDescription; |
547 | public: |
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 | |
569 | atomic_t g_ExceptionCount; |
570 | atomic_t g_TaskCount; |
571 | unsigned g_ExecutedAtCancellation; |
572 | bool g_Rethrow; |
573 | bool 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 | |
590 | inline |
591 | void 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 | |
602 | class ThrowingTask : NoAssign, Harness::NoAfterlife { |
603 | atomic_t &m_TaskCount; |
604 | public: |
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 | |
622 | void 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 |
650 | void 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 | |
665 | void 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 | |
691 | void 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 | |
717 | void 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 | |
755 | class StructuredCancellationTestDriver { |
756 | tbb::aligned_space<handle_type,NUM_CHORES> m_handles; |
757 | |
758 | public: |
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 | |
784 | void 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 | |
800 | template<bool Throw> |
801 | void 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 | |
829 | void EmptyFunction () {} |
830 | |
831 | void 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 | |
841 | struct TestFunctor { |
842 | void operator()() { ASSERT( false, "Non-const operator called" ); } |
843 | void operator()() const { /* library requires this overload only */ } |
844 | }; |
845 | |
846 | void 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 |
854 | namespace 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 | |
951 | void 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 | |
963 | int 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 | |
1020 | int TestMain () { |
1021 | return Harness::Skipped; |
1022 | } |
1023 | |
1024 | #endif /* !__TBB_TASK_GROUP_CONTEXT */ |
1025 | |