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#define HARNESS_DEFAULT_MIN_THREADS 2
18#define HARNESS_DEFAULT_MAX_THREADS 4
19#define __TBB_SHUFFLE_PRESENT (_MSC_VER >= 1910 || __cplusplus >= 201103L && (__TBB_GLIBCXX_VERSION >= 40800 || _LIBCPP_VERSION))
20
21#include "harness.h"
22
23#if __TBB_SHUFFLE_PRESENT
24#include <random>
25#endif
26
27#if __TBB_TASK_GROUP_CONTEXT
28
29#define __TBB_ATOMICS_CODEGEN_BROKEN __SUNPRO_CC
30
31#define private public
32#include "tbb/task.h"
33#undef private
34
35#include "tbb/task_scheduler_init.h"
36#include "tbb/spin_mutex.h"
37#include "tbb/tick_count.h"
38
39#include <string>
40
41#define NUM_CHILD_TASKS 256
42#define NUM_ROOT_TASKS 32
43#define NUM_ROOTS_IN_GROUP 8
44
45//! Statistics about number of tasks in different states
46class TaskStats {
47 typedef tbb::spin_mutex::scoped_lock lock_t;
48 //! Number of tasks allocated that was ever allocated
49 volatile intptr_t m_Existed;
50 //! Number of tasks executed to the moment
51 volatile intptr_t m_Executed;
52 //! Number of tasks allocated but not yet destroyed to the moment
53 volatile intptr_t m_Existing;
54
55 mutable tbb::spin_mutex m_Mutex;
56public:
57 //! Assumes that assignment is noncontended for the left-hand operand
58 const TaskStats& operator= ( const TaskStats& rhs ) {
59 if ( this != &rhs ) {
60 lock_t lock(rhs.m_Mutex);
61 m_Existed = rhs.m_Existed;
62 m_Executed = rhs.m_Executed;
63 m_Existing = rhs.m_Existing;
64 }
65 return *this;
66 }
67 intptr_t Existed() const { return m_Existed; }
68 intptr_t Executed() const { return m_Executed; }
69 intptr_t Existing() const { return m_Existing; }
70 void IncExisted() { lock_t lock(m_Mutex); ++m_Existed; ++m_Existing; }
71 void IncExecuted() { lock_t lock(m_Mutex); ++m_Executed; }
72 void DecExisting() { lock_t lock(m_Mutex); --m_Existing; }
73 //! Assumed to be used in uncontended manner only
74 void Reset() { m_Executed = m_Existing = m_Existed = 0; }
75};
76
77TaskStats g_CurStat;
78
79inline intptr_t Existed () { return g_CurStat.Existed(); }
80
81#include "harness_eh.h"
82
83bool g_BoostExecutedCount = true;
84volatile bool g_TaskWasCancelled = false;
85
86inline void ResetGlobals () {
87 ResetEhGlobals();
88 g_BoostExecutedCount = true;
89 g_TaskWasCancelled = false;
90 g_CurStat.Reset();
91}
92
93#define ASSERT_TEST_POSTCOND() \
94 ASSERT (g_CurStat.Existed() >= g_CurStat.Executed(), "Total number of tasks is less than executed"); \
95 ASSERT (!g_CurStat.Existing(), "Not all task objects have been destroyed"); \
96 ASSERT (!tbb::task::self().is_cancelled(), "Scheduler's default context has not been cleaned up properly");
97
98inline void WaitForException () {
99 int n = 0;
100 while ( ++n < c_Timeout && !__TBB_load_with_acquire(g_ExceptionCaught) )
101 __TBB_Yield();
102 ASSERT_WARNING( n < c_Timeout, "WaitForException failed" );
103}
104
105class TaskBase : public tbb::task {
106 tbb::task* execute () __TBB_override {
107 tbb::task* t = NULL;
108 __TBB_TRY {
109 t = do_execute();
110 } __TBB_CATCH( ... ) {
111 g_CurStat.IncExecuted();
112 __TBB_RETHROW();
113 }
114 g_CurStat.IncExecuted();
115 return t;
116 }
117protected:
118 TaskBase ( bool throwException = true ) : m_Throw(throwException) { g_CurStat.IncExisted(); }
119 ~TaskBase () { g_CurStat.DecExisting(); }
120
121 virtual tbb::task* do_execute () = 0;
122
123 bool m_Throw;
124}; // class TaskBase
125
126class LeafTask : public TaskBase {
127 tbb::task* do_execute () __TBB_override {
128 Harness::ConcurrencyTracker ct;
129 WaitUntilConcurrencyPeaks();
130 if ( g_BoostExecutedCount )
131 ++g_CurExecuted;
132 if ( m_Throw )
133 ThrowTestException(NUM_CHILD_TASKS/2);
134 if ( !g_ThrowException )
135 __TBB_Yield();
136 return NULL;
137 }
138public:
139 LeafTask ( bool throw_exception = true ) : TaskBase(throw_exception) {}
140};
141
142class SimpleRootTask : public TaskBase {
143 tbb::task* do_execute () __TBB_override {
144 set_ref_count(NUM_CHILD_TASKS + 1);
145 for ( size_t i = 0; i < NUM_CHILD_TASKS; ++i )
146 spawn( *new( allocate_child() ) LeafTask(m_Throw) );
147 wait_for_all();
148 return NULL;
149 }
150public:
151 SimpleRootTask ( bool throw_exception = true ) : TaskBase(throw_exception) {}
152};
153
154#if TBB_USE_EXCEPTIONS
155
156class SimpleThrowingTask : public tbb::task {
157public:
158 tbb::task* execute () __TBB_override { throw 0; }
159 ~SimpleThrowingTask() {}
160};
161
162//! Checks if innermost running task information is updated correctly during cancellation processing
163void Test0 () {
164 tbb::task_scheduler_init init (1);
165 tbb::empty_task &r = *new( tbb::task::allocate_root() ) tbb::empty_task;
166 tbb::task_list tl;
167 tl.push_back( *new( r.allocate_child() ) SimpleThrowingTask );
168 tl.push_back( *new( r.allocate_child() ) SimpleThrowingTask );
169 r.set_ref_count( 3 );
170 try {
171 r.spawn_and_wait_for_all( tl );
172 }
173 catch (...) {}
174 r.destroy( r );
175}
176
177//! Default exception behavior test.
178/** Allocates a root task that spawns a bunch of children, one or several of which throw
179 a test exception in a worker or master thread (depending on the global setting). **/
180void Test1 () {
181 ResetGlobals();
182 tbb::empty_task &r = *new( tbb::task::allocate_root() ) tbb::empty_task;
183 ASSERT (!g_CurStat.Existing() && !g_CurStat.Existed() && !g_CurStat.Executed(),
184 "something wrong with the task accounting");
185 r.set_ref_count(NUM_CHILD_TASKS + 1);
186 for ( int i = 0; i < NUM_CHILD_TASKS; ++i )
187 r.spawn( *new( r.allocate_child() ) LeafTask );
188 TRY();
189 r.wait_for_all();
190 CATCH_AND_ASSERT();
191 r.destroy(r);
192 ASSERT_TEST_POSTCOND();
193} // void Test1 ()
194
195//! Default exception behavior test.
196/** Allocates and spawns root task that runs a bunch of children, one of which throws
197 a test exception in a worker thread. (Similar to Test1, except that the root task
198 is spawned by the test function, and children are created by the root task instead
199 of the test function body.) **/
200void Test2 () {
201 ResetGlobals();
202 SimpleRootTask &r = *new( tbb::task::allocate_root() ) SimpleRootTask;
203 ASSERT (g_CurStat.Existing() == 1 && g_CurStat.Existed() == 1 && !g_CurStat.Executed(),
204 "something wrong with the task accounting");
205 TRY();
206 tbb::task::spawn_root_and_wait(r);
207 CATCH_AND_ASSERT();
208 ASSERT (g_ExceptionCaught, "no exception occurred");
209 ASSERT_TEST_POSTCOND();
210} // void Test2 ()
211
212//! The same as Test2() except the root task has explicit context.
213/** The context is initialized as bound in order to check correctness of its associating
214 with a root task. **/
215void Test3 () {
216 ResetGlobals();
217 tbb::task_group_context ctx(tbb::task_group_context::bound);
218 SimpleRootTask &r = *new( tbb::task::allocate_root(ctx) ) SimpleRootTask;
219 ASSERT (g_CurStat.Existing() == 1 && g_CurStat.Existed() == 1 && !g_CurStat.Executed(),
220 "something wrong with the task accounting");
221 TRY();
222 tbb::task::spawn_root_and_wait(r);
223 CATCH_AND_ASSERT();
224 ASSERT (g_ExceptionCaught, "no exception occurred");
225 ASSERT_TEST_POSTCOND();
226} // void Test2 ()
227
228class RootLauncherTask : public TaskBase {
229 tbb::task_group_context::kind_type m_CtxKind;
230
231 tbb::task* do_execute () __TBB_override {
232 tbb::task_group_context ctx(m_CtxKind);
233 SimpleRootTask &r = *new( allocate_root() ) SimpleRootTask;
234 r.change_group(ctx);
235 TRY();
236 spawn_root_and_wait(r);
237 // Give a child of our siblings a chance to throw the test exception
238 WaitForException();
239 CATCH();
240 ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || !g_UnknownException, "unknown exception was caught");
241 return NULL;
242 }
243public:
244 RootLauncherTask ( tbb::task_group_context::kind_type ctx_kind = tbb::task_group_context::isolated ) : m_CtxKind(ctx_kind) {}
245};
246
247/** Allocates and spawns a bunch of roots, which allocate and spawn new root with
248 isolated context, which at last spawns a bunch of children each, one of which
249 throws a test exception in a worker thread. **/
250void Test4 () {
251 ResetGlobals();
252 tbb::task_list tl;
253 for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i )
254 tl.push_back( *new( tbb::task::allocate_root() ) RootLauncherTask );
255 TRY();
256 tbb::task::spawn_root_and_wait(tl);
257 CATCH_AND_ASSERT();
258 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "exception in this scope is unexpected");
259 intptr_t num_tasks_expected = NUM_ROOT_TASKS * (NUM_CHILD_TASKS + 2);
260 ASSERT (g_CurStat.Existed() == num_tasks_expected, "Wrong total number of tasks");
261 if ( g_SolitaryException )
262 ASSERT (g_CurStat.Executed() >= num_tasks_expected - NUM_CHILD_TASKS, "Unexpected number of executed tasks");
263 ASSERT_TEST_POSTCOND();
264} // void Test4 ()
265
266/** The same as Test4, except the contexts are bound. **/
267void Test4_1 () {
268 ResetGlobals();
269 tbb::task_list tl;
270 for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i )
271 tl.push_back( *new( tbb::task::allocate_root() ) RootLauncherTask(tbb::task_group_context::bound) );
272 TRY();
273 tbb::task::spawn_root_and_wait(tl);
274 CATCH_AND_ASSERT();
275 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "exception in this scope is unexpected");
276 intptr_t num_tasks_expected = NUM_ROOT_TASKS * (NUM_CHILD_TASKS + 2);
277 ASSERT (g_CurStat.Existed() == num_tasks_expected, "Wrong total number of tasks");
278 if ( g_SolitaryException )
279 ASSERT (g_CurStat.Executed() >= num_tasks_expected - NUM_CHILD_TASKS, "Unexpected number of executed tasks");
280 ASSERT_TEST_POSTCOND();
281} // void Test4_1 ()
282
283
284class RootsGroupLauncherTask : public TaskBase {
285 tbb::task* do_execute () __TBB_override {
286 tbb::task_group_context ctx (tbb::task_group_context::isolated);
287 tbb::task_list tl;
288 for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i )
289 tl.push_back( *new( allocate_root(ctx) ) SimpleRootTask );
290 TRY();
291 spawn_root_and_wait(tl);
292 // Give worker a chance to throw exception
293 WaitForException();
294 CATCH_AND_ASSERT();
295 return NULL;
296 }
297};
298
299/** Allocates and spawns a bunch of roots, which allocate and spawn groups of roots
300 with an isolated context shared by all group members, which at last spawn a bunch
301 of children each, one of which throws a test exception in a worker thread. **/
302void Test5 () {
303 ResetGlobals();
304 tbb::task_list tl;
305 for ( size_t i = 0; i < NUM_ROOTS_IN_GROUP; ++i )
306 tl.push_back( *new( tbb::task::allocate_root() ) RootsGroupLauncherTask );
307 TRY();
308 tbb::task::spawn_root_and_wait(tl);
309 CATCH_AND_ASSERT();
310 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "unexpected exception intercepted");
311 if ( g_SolitaryException ) {
312 intptr_t num_tasks_expected = NUM_ROOTS_IN_GROUP * (1 + NUM_ROOT_TASKS * (1 + NUM_CHILD_TASKS));
313 intptr_t min_num_tasks_executed = num_tasks_expected - NUM_ROOT_TASKS * (NUM_CHILD_TASKS + 1);
314 ASSERT (g_CurStat.Executed() >= min_num_tasks_executed, "Too few tasks executed");
315 }
316 ASSERT_TEST_POSTCOND();
317} // void Test5 ()
318
319class ThrowingRootLauncherTask : public TaskBase {
320 tbb::task* do_execute () __TBB_override {
321 tbb::task_group_context ctx (tbb::task_group_context::bound);
322 SimpleRootTask &r = *new( allocate_root(ctx) ) SimpleRootTask(false);
323 TRY();
324 spawn_root_and_wait(r);
325 CATCH();
326 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "unexpected exception intercepted");
327 ThrowTestException(NUM_CHILD_TASKS);
328 g_TaskWasCancelled |= is_cancelled();
329 return NULL;
330 }
331};
332
333class BoundHierarchyLauncherTask : public TaskBase {
334 bool m_Recover;
335
336 void alloc_roots ( tbb::task_group_context& ctx, tbb::task_list& tl ) {
337 for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i )
338 tl.push_back( *new( allocate_root(ctx) ) ThrowingRootLauncherTask );
339 }
340
341 tbb::task* do_execute () __TBB_override {
342 tbb::task_group_context ctx (tbb::task_group_context::isolated);
343 tbb::task_list tl;
344 alloc_roots(ctx, tl);
345 TRY();
346 spawn_root_and_wait(tl);
347 CATCH_AND_ASSERT();
348 ASSERT (l_ExceptionCaughtAtCurrentLevel, "no exception occurred");
349 ASSERT (!tl.empty(), "task list was cleared somehow");
350 if ( g_SolitaryException )
351 ASSERT (g_TaskWasCancelled, "No tasks were cancelled despite of exception");
352 if ( m_Recover ) {
353 // Test task_group_context::unbind and task_group_context::reset methods
354 g_ThrowException = false;
355 l_ExceptionCaughtAtCurrentLevel = false;
356 tl.clear();
357 alloc_roots(ctx, tl);
358 ctx.reset();
359 try {
360 spawn_root_and_wait(tl);
361 }
362 catch (...) {
363 l_ExceptionCaughtAtCurrentLevel = true;
364 }
365 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "unexpected exception occurred");
366 }
367 return NULL;
368 }
369public:
370 BoundHierarchyLauncherTask ( bool recover = false ) : m_Recover(recover) {}
371
372}; // class BoundHierarchyLauncherTask
373
374//! Test for bound contexts forming 2 level tree. Exception is thrown on the 1st (root) level.
375/** Allocates and spawns a root that spawns a bunch of 2nd level roots sharing
376 the same isolated context, each of which in their turn spawns a single 3rd level
377 root with the bound context, and these 3rd level roots spawn bunches of leaves
378 in the end. Leaves do not generate exceptions. The test exception is generated
379 by one of the 2nd level roots. **/
380void Test6 () {
381 ResetGlobals();
382 BoundHierarchyLauncherTask &r = *new( tbb::task::allocate_root() ) BoundHierarchyLauncherTask;
383 TRY();
384 tbb::task::spawn_root_and_wait(r);
385 CATCH_AND_ASSERT();
386 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "unexpected exception intercepted");
387 // After the first of the branches (ThrowingRootLauncherTask) completes,
388 // the rest of the task tree may be collapsed before having a chance to execute leaves.
389 // A number of branches running concurrently with the first one will be able to spawn leaves though.
390 /// \todo: If additional checkpoints are added to scheduler the following assertion must weaken
391 intptr_t num_tasks_expected = 1 + NUM_ROOT_TASKS * (2 + NUM_CHILD_TASKS);
392 intptr_t min_num_tasks_created = 1 + g_NumThreads * 2 + NUM_CHILD_TASKS;
393 // 2 stands for BoundHierarchyLauncherTask and SimpleRootTask
394 // 1 corresponds to BoundHierarchyLauncherTask
395 intptr_t min_num_tasks_executed = 2 + 1 + NUM_CHILD_TASKS;
396 ASSERT (g_CurStat.Existed() <= num_tasks_expected, "Number of expected tasks is calculated incorrectly");
397 ASSERT (g_CurStat.Existed() >= min_num_tasks_created, "Too few tasks created");
398 ASSERT (g_CurStat.Executed() >= min_num_tasks_executed, "Too few tasks executed");
399 ASSERT_TEST_POSTCOND();
400} // void Test6 ()
401
402//! Tests task_group_context::unbind and task_group_context::reset methods.
403/** Allocates and spawns a root that spawns a bunch of 2nd level roots sharing
404 the same isolated context, each of which in their turn spawns a single 3rd level
405 root with the bound context, and these 3rd level roots spawn bunches of leaves
406 in the end. Leaves do not generate exceptions. The test exception is generated
407 by one of the 2nd level roots. **/
408void Test7 () {
409 ResetGlobals();
410 BoundHierarchyLauncherTask &r = *new( tbb::task::allocate_root() ) BoundHierarchyLauncherTask;
411 TRY();
412 tbb::task::spawn_root_and_wait(r);
413 CATCH_AND_ASSERT();
414 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "unexpected exception intercepted");
415 ASSERT_TEST_POSTCOND();
416} // void Test6 ()
417
418class BoundHierarchyLauncherTask2 : public TaskBase {
419 tbb::task* do_execute () __TBB_override {
420 tbb::task_group_context ctx;
421 tbb::task_list tl;
422 for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i )
423 tl.push_back( *new( allocate_root(ctx) ) RootLauncherTask(tbb::task_group_context::bound) );
424 TRY();
425 spawn_root_and_wait(tl);
426 CATCH_AND_ASSERT();
427 // Exception must be intercepted by RootLauncherTask
428 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "no exception occurred");
429 return NULL;
430 }
431}; // class BoundHierarchyLauncherTask2
432
433//! Test for bound contexts forming 2 level tree. Exception is thrown in the 2nd (outer) level.
434/** Allocates and spawns a root that spawns a bunch of 2nd level roots sharing
435 the same isolated context, each of which in their turn spawns a single 3rd level
436 root with the bound context, and these 3rd level roots spawn bunches of leaves
437 in the end. The test exception is generated by one of the leaves. **/
438void Test8 () {
439 ResetGlobals();
440 BoundHierarchyLauncherTask2 &r = *new( tbb::task::allocate_root() ) BoundHierarchyLauncherTask2;
441 TRY();
442 tbb::task::spawn_root_and_wait(r);
443 CATCH_AND_ASSERT();
444 ASSERT (!l_ExceptionCaughtAtCurrentLevel, "unexpected exception intercepted");
445 if ( g_SolitaryException ) {
446 intptr_t num_tasks_expected = 1 + NUM_ROOT_TASKS * (2 + NUM_CHILD_TASKS);
447 intptr_t min_num_tasks_created = 1 + g_NumThreads * (2 + NUM_CHILD_TASKS);
448 intptr_t min_num_tasks_executed = num_tasks_expected - (NUM_CHILD_TASKS + 1);
449 ASSERT (g_CurStat.Existed() <= num_tasks_expected, "Number of expected tasks is calculated incorrectly");
450 ASSERT (g_CurStat.Existed() >= min_num_tasks_created, "Too few tasks created");
451 ASSERT (g_CurStat.Executed() >= min_num_tasks_executed, "Too few tasks executed");
452 }
453 ASSERT_TEST_POSTCOND();
454} // void Test8 ()
455
456template<typename T>
457void ThrowMovableException ( intptr_t threshold, const T& data ) {
458 if ( !IsThrowingThread() )
459 return;
460 if ( !g_SolitaryException ) {
461#if __TBB_ATOMICS_CODEGEN_BROKEN
462 g_ExceptionsThrown = g_ExceptionsThrown + 1;
463#else
464 ++g_ExceptionsThrown;
465#endif
466 throw tbb::movable_exception<T>(data);
467 }
468 while ( g_CurStat.Existed() < threshold )
469 __TBB_Yield();
470 if ( g_ExceptionsThrown.compare_and_swap(1, 0) == 0 )
471 throw tbb::movable_exception<T>(data);
472}
473
474const int g_IntExceptionData = -375;
475const std::string g_StringExceptionData = "My test string";
476
477// Exception data class implementing minimal requirements of tbb::movable_exception
478class ExceptionData {
479 const ExceptionData& operator = ( const ExceptionData& src );
480 explicit ExceptionData ( int n ) : m_Int(n), m_String(g_StringExceptionData) {}
481public:
482 ExceptionData ( const ExceptionData& src ) : m_Int(src.m_Int), m_String(src.m_String) {}
483 ~ExceptionData () {}
484
485 int m_Int;
486 std::string m_String;
487
488 // Simple way to provide an instance when all initializing constructors are private
489 // and to avoid memory reclamation problems.
490 static ExceptionData s_data;
491};
492
493ExceptionData ExceptionData::s_data(g_IntExceptionData);
494
495typedef tbb::movable_exception<int> SolitaryMovableException;
496typedef tbb::movable_exception<ExceptionData> MultipleMovableException;
497
498class LeafTaskWithMovableExceptions : public TaskBase {
499 tbb::task* do_execute () __TBB_override {
500 Harness::ConcurrencyTracker ct;
501 WaitUntilConcurrencyPeaks();
502 if ( g_SolitaryException )
503 ThrowMovableException<int>(NUM_CHILD_TASKS/2, g_IntExceptionData);
504 else
505 ThrowMovableException<ExceptionData>(NUM_CHILD_TASKS/2, ExceptionData::s_data);
506 return NULL;
507 }
508};
509
510void CheckException ( tbb::tbb_exception& e ) {
511 ASSERT (strcmp(e.name(), (g_SolitaryException ? typeid(SolitaryMovableException)
512 : typeid(MultipleMovableException)).name() ) == 0,
513 "Unexpected original exception name");
514 ASSERT (strcmp(e.what(), "tbb::movable_exception") == 0, "Unexpected original exception info ");
515 if ( g_SolitaryException ) {
516 SolitaryMovableException& me = dynamic_cast<SolitaryMovableException&>(e);
517 ASSERT (me.data() == g_IntExceptionData, "Unexpected solitary movable_exception data");
518 }
519 else {
520 MultipleMovableException& me = dynamic_cast<MultipleMovableException&>(e);
521 ASSERT (me.data().m_Int == g_IntExceptionData, "Unexpected multiple movable_exception int data");
522 ASSERT (me.data().m_String == g_StringExceptionData, "Unexpected multiple movable_exception string data");
523 }
524}
525
526void CheckException () {
527 try {
528 throw;
529 } catch ( tbb::tbb_exception& e ) {
530 CheckException(e);
531 }
532 catch ( ... ) {
533 }
534}
535
536//! Test for movable_exception behavior, and external exception recording.
537/** Allocates a root task that spawns a bunch of children, one or several of which throw
538 a movable exception in a worker or master thread (depending on the global settings).
539 The test also checks the correctness of multiple rethrowing of the pending exception. **/
540void TestMovableException () {
541 REMARK( "TestMovableException\n" );
542 ResetGlobals();
543 bool bUnsupported = false;
544 tbb::task_group_context ctx;
545 tbb::empty_task *r = new( tbb::task::allocate_root() ) tbb::empty_task;
546 ASSERT (!g_CurStat.Existing() && !g_CurStat.Existed() && !g_CurStat.Executed(),
547 "something wrong with the task accounting");
548 r->set_ref_count(NUM_CHILD_TASKS + 1);
549 for ( int i = 0; i < NUM_CHILD_TASKS; ++i )
550 r->spawn( *new( r->allocate_child() ) LeafTaskWithMovableExceptions );
551 TRY()
552 r->wait_for_all();
553 } catch ( ... ) {
554 ASSERT (!ctx.is_group_execution_cancelled(), "");
555 CheckException();
556 try {
557 throw;
558 } catch ( tbb::tbb_exception& e ) {
559 CheckException(e);
560 g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true;
561 }
562 catch ( ... ) {
563 g_ExceptionCaught = true;
564 g_UnknownException = unknownException = true;
565 }
566 try {
567 ctx.register_pending_exception();
568 } catch ( ... ) {
569 bUnsupported = true;
570 REPORT( "Warning: register_pending_exception() failed. This is expected in case of linking with static msvcrt\n" );
571 }
572 ASSERT (ctx.is_group_execution_cancelled() || bUnsupported, "After exception registration the context must be in the cancelled state");
573 }
574 r->destroy(*r);
575 ASSERT_EXCEPTION();
576 ASSERT_TEST_POSTCOND();
577
578 r = new( tbb::task::allocate_root(ctx) ) tbb::empty_task;
579 r->set_ref_count(1);
580 g_ExceptionCaught = g_UnknownException = false;
581 try {
582 r->wait_for_all();
583 } catch ( tbb::tbb_exception& e ) {
584 CheckException(e);
585 g_ExceptionCaught = true;
586 }
587 catch ( ... ) {
588 g_ExceptionCaught = true;
589 g_UnknownException = true;
590 }
591 ASSERT (g_ExceptionCaught || bUnsupported, "no exception occurred");
592 ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || !g_UnknownException || bUnsupported, "unknown exception was caught");
593 r->destroy(*r);
594} // void Test10 ()
595
596#endif /* TBB_USE_EXCEPTIONS */
597
598template<class T>
599class CtxLauncherTask : public tbb::task {
600 tbb::task_group_context &m_Ctx;
601
602 tbb::task* execute () __TBB_override {
603 spawn_root_and_wait( *new( allocate_root(m_Ctx) ) T );
604 return NULL;
605 }
606public:
607 CtxLauncherTask ( tbb::task_group_context& ctx ) : m_Ctx(ctx) {}
608};
609
610//! Test for cancelling a task hierarchy from outside (from a task running in parallel with it).
611void TestCancelation () {
612 ResetGlobals();
613 g_ThrowException = false;
614 tbb::task_group_context ctx;
615 tbb::task_list tl;
616 tl.push_back( *new( tbb::task::allocate_root() ) CtxLauncherTask<SimpleRootTask>(ctx) );
617 tl.push_back( *new( tbb::task::allocate_root() ) CancellatorTask(ctx, NUM_CHILD_TASKS / 4) );
618 TRY();
619 tbb::task::spawn_root_and_wait(tl);
620 CATCH_AND_FAIL();
621 ASSERT (g_CurStat.Executed() <= g_ExecutedAtLastCatch + g_NumThreads, "Too many tasks were executed after cancellation");
622 ASSERT_TEST_POSTCOND();
623} // void Test9 ()
624
625class CtxDestroyerTask : public tbb::task {
626 int m_nestingLevel;
627
628 tbb::task* execute () __TBB_override {
629 ASSERT ( m_nestingLevel >= 0 && m_nestingLevel < MaxNestingDepth, "Wrong nesting level. The test is broken" );
630 tbb::task_group_context ctx;
631 tbb::task *t = new( allocate_root(ctx) ) tbb::empty_task;
632 int level = ++m_nestingLevel;
633 if ( level < MaxNestingDepth ) {
634 execute();
635 }
636 else {
637 if ( !CancellatorTask::WaitUntilReady() )
638 REPORT( "Warning: missing wakeup\n" );
639 ++g_CurExecuted;
640 }
641 if ( ctx.is_group_execution_cancelled() )
642 ++s_numCancelled;
643 t->destroy(*t);
644 return NULL;
645 }
646public:
647 CtxDestroyerTask () : m_nestingLevel(0) { s_numCancelled = 0; }
648
649 static const int MaxNestingDepth = 256;
650 static int s_numCancelled;
651};
652
653int CtxDestroyerTask::s_numCancelled = 0;
654
655//! Test for data race between cancellation propagation and context destruction.
656/** If the data race ever occurs, an assertion inside TBB will be triggered. **/
657void TestCtxDestruction () {
658 REMARK( "TestCtxDestruction\n" );
659 for ( size_t i = 0; i < 10; ++i ) {
660 tbb::task_group_context ctx;
661 tbb::task_list tl;
662 ResetGlobals();
663 g_BoostExecutedCount = false;
664 g_ThrowException = false;
665 CancellatorTask::Reset();
666
667 tl.push_back( *new( tbb::task::allocate_root() ) CtxLauncherTask<CtxDestroyerTask>(ctx) );
668 tl.push_back( *new( tbb::task::allocate_root() ) CancellatorTask(ctx, 1) );
669 tbb::task::spawn_root_and_wait(tl);
670 ASSERT( g_CurExecuted == 1, "Test is broken" );
671 ASSERT( CtxDestroyerTask::s_numCancelled <= CtxDestroyerTask::MaxNestingDepth, "Test is broken" );
672 }
673} // void TestCtxDestruction()
674
675#include <algorithm>
676#include "harness_barrier.h"
677
678class CtxConcurrentDestroyer : NoAssign, Harness::NoAfterlife {
679 static const int ContextsPerThread = 512;
680
681 static int s_Concurrency;
682 static int s_NumContexts;
683 static tbb::task_group_context** s_Contexts;
684 static char* s_Buffer;
685 static Harness::SpinBarrier s_Barrier;
686 static Harness::SpinBarrier s_ExitBarrier;
687
688 struct Shuffler {
689 void operator() () const {
690#if __TBB_SHUFFLE_PRESENT
691 std::shuffle(s_Contexts, s_Contexts + s_NumContexts, std::mt19937(std::random_device()()));
692#else
693 std::random_shuffle(s_Contexts, s_Contexts + s_NumContexts);
694#endif
695 }
696 };
697public:
698 static void Init ( int p ) {
699 s_Concurrency = p;
700 s_NumContexts = p * ContextsPerThread;
701 s_Contexts = new tbb::task_group_context*[s_NumContexts];
702 s_Buffer = new char[s_NumContexts * sizeof(tbb::task_group_context)];
703 s_Barrier.initialize( p );
704 s_ExitBarrier.initialize( p );
705 }
706 static void Uninit () {
707 for ( int i = 0; i < s_NumContexts; ++i ) {
708 tbb::internal::context_list_node_t &node = s_Contexts[i]->my_node;
709 ASSERT( !node.my_next && !node.my_prev, "Destroyed context was written to during context chain update" );
710 }
711 delete []s_Contexts;
712 delete []s_Buffer;
713 }
714
715 void operator() ( int id ) const {
716 int begin = ContextsPerThread * id,
717 end = begin + ContextsPerThread;
718 for ( int i = begin; i < end; ++i )
719 s_Contexts[i] = new( s_Buffer + i * sizeof(tbb::task_group_context) ) tbb::task_group_context;
720 s_Barrier.wait( Shuffler() );
721 for ( int i = begin; i < end; ++i ) {
722 s_Contexts[i]->tbb::task_group_context::~task_group_context();
723 memset( static_cast<void*>(s_Contexts[i]), 0, sizeof(tbb::task_group_context) );
724 }
725 s_ExitBarrier.wait();
726 }
727}; // class CtxConcurrentDestroyer
728
729int CtxConcurrentDestroyer::s_Concurrency;
730int CtxConcurrentDestroyer::s_NumContexts;
731tbb::task_group_context** CtxConcurrentDestroyer::s_Contexts;
732char* CtxConcurrentDestroyer::s_Buffer;
733Harness::SpinBarrier CtxConcurrentDestroyer::s_Barrier;
734Harness::SpinBarrier CtxConcurrentDestroyer::s_ExitBarrier;
735
736void TestConcurrentCtxDestruction () {
737 REMARK( "TestConcurrentCtxDestruction\n" );
738 CtxConcurrentDestroyer::Init(g_NumThreads);
739 NativeParallelFor( g_NumThreads, CtxConcurrentDestroyer() );
740 CtxConcurrentDestroyer::Uninit();
741}
742
743void RunTests () {
744 REMARK ("Number of threads %d\n", g_NumThreads);
745 tbb::task_scheduler_init init (g_NumThreads);
746 g_Master = Harness::CurrentTid();
747#if TBB_USE_EXCEPTIONS
748 Test1();
749 Test2();
750 Test3();
751 Test4();
752 Test4_1();
753 Test5();
754 Test6();
755 Test7();
756 Test8();
757 TestMovableException();
758#endif /* TBB_USE_EXCEPTIONS */
759 TestCancelation();
760 TestCtxDestruction();
761#if !RML_USE_WCRM
762 TestConcurrentCtxDestruction();
763#endif
764}
765
766int TestMain () {
767 REMARK ("Using %s\n", TBB_USE_CAPTURED_EXCEPTION ? "tbb:captured_exception" : "exact exception propagation");
768 MinThread = min(NUM_ROOTS_IN_GROUP, min(tbb::task_scheduler_init::default_num_threads(), max(2, MinThread)));
769 MaxThread = min(NUM_ROOTS_IN_GROUP, max(MinThread, min(tbb::task_scheduler_init::default_num_threads(), MaxThread)));
770 ASSERT (NUM_ROOTS_IN_GROUP < NUM_ROOT_TASKS, "Fix defines");
771#if TBB_USE_EXCEPTIONS
772 // Test0 always runs on one thread
773 Test0();
774#endif /* TBB_USE_EXCEPTIONS */
775 g_SolitaryException = 0;
776 for ( g_NumThreads = MinThread; g_NumThreads <= MaxThread; ++g_NumThreads )
777 RunTests();
778 return Harness::Done;
779}
780
781#else /* !__TBB_TASK_GROUP_CONTEXT */
782
783int TestMain () {
784 return Harness::Skipped;
785}
786
787#endif /* !__TBB_TASK_GROUP_CONTEXT */
788