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 |
46 | class 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; |
56 | public: |
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 | |
77 | TaskStats g_CurStat; |
78 | |
79 | inline intptr_t Existed () { return g_CurStat.Existed(); } |
80 | |
81 | #include "harness_eh.h" |
82 | |
83 | bool g_BoostExecutedCount = true; |
84 | volatile bool g_TaskWasCancelled = false; |
85 | |
86 | inline 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 | |
98 | inline 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 | |
105 | class 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 | } |
117 | protected: |
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 | |
126 | class 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 | } |
138 | public: |
139 | LeafTask ( bool throw_exception = true ) : TaskBase(throw_exception) {} |
140 | }; |
141 | |
142 | class 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 | } |
150 | public: |
151 | SimpleRootTask ( bool throw_exception = true ) : TaskBase(throw_exception) {} |
152 | }; |
153 | |
154 | #if TBB_USE_EXCEPTIONS |
155 | |
156 | class SimpleThrowingTask : public tbb::task { |
157 | public: |
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 |
163 | void 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). **/ |
180 | void 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.) **/ |
200 | void 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. **/ |
215 | void 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 | |
228 | class 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 | } |
243 | public: |
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. **/ |
250 | void 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. **/ |
267 | void 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 | |
284 | class 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. **/ |
302 | void 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 | |
319 | class 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 | |
333 | class 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 | } |
369 | public: |
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. **/ |
380 | void 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. **/ |
408 | void 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 | |
418 | class 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. **/ |
438 | void 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 | |
456 | template<typename T> |
457 | void 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 | |
474 | const int g_IntExceptionData = -375; |
475 | const std::string g_StringExceptionData = "My test string" ; |
476 | |
477 | // Exception data class implementing minimal requirements of tbb::movable_exception |
478 | class ExceptionData { |
479 | const ExceptionData& operator = ( const ExceptionData& src ); |
480 | explicit ExceptionData ( int n ) : m_Int(n), m_String(g_StringExceptionData) {} |
481 | public: |
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 | |
493 | ExceptionData ExceptionData::s_data(g_IntExceptionData); |
494 | |
495 | typedef tbb::movable_exception<int> SolitaryMovableException; |
496 | typedef tbb::movable_exception<ExceptionData> MultipleMovableException; |
497 | |
498 | class 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 | |
510 | void 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 | |
526 | void 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. **/ |
540 | void 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 | |
598 | template<class T> |
599 | class 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 | } |
606 | public: |
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). |
611 | void 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 | |
625 | class 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 | } |
646 | public: |
647 | CtxDestroyerTask () : m_nestingLevel(0) { s_numCancelled = 0; } |
648 | |
649 | static const int MaxNestingDepth = 256; |
650 | static int s_numCancelled; |
651 | }; |
652 | |
653 | int 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. **/ |
657 | void 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 | |
678 | class 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 | }; |
697 | public: |
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 | |
729 | int CtxConcurrentDestroyer::s_Concurrency; |
730 | int CtxConcurrentDestroyer::s_NumContexts; |
731 | tbb::task_group_context** CtxConcurrentDestroyer::s_Contexts; |
732 | char* CtxConcurrentDestroyer::s_Buffer; |
733 | Harness::SpinBarrier CtxConcurrentDestroyer::s_Barrier; |
734 | Harness::SpinBarrier CtxConcurrentDestroyer::s_ExitBarrier; |
735 | |
736 | void TestConcurrentCtxDestruction () { |
737 | REMARK( "TestConcurrentCtxDestruction\n" ); |
738 | CtxConcurrentDestroyer::Init(g_NumThreads); |
739 | NativeParallelFor( g_NumThreads, CtxConcurrentDestroyer() ); |
740 | CtxConcurrentDestroyer::Uninit(); |
741 | } |
742 | |
743 | void 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 | |
766 | int 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 | |
783 | int TestMain () { |
784 | return Harness::Skipped; |
785 | } |
786 | |
787 | #endif /* !__TBB_TASK_GROUP_CONTEXT */ |
788 | |