| /* |
| Copyright 2005-2010 Intel Corporation. All Rights Reserved. |
| |
| This file is part of Threading Building Blocks. |
| |
| Threading Building Blocks is free software; you can redistribute it |
| and/or modify it under the terms of the GNU General Public License |
| version 2 as published by the Free Software Foundation. |
| |
| Threading Building Blocks is distributed in the hope that it will be |
| useful, but WITHOUT ANY WARRANTY; without even the implied warranty |
| of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with Threading Building Blocks; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| |
| As a special exception, you may use this file as part of a free software |
| library without restriction. Specifically, if other files instantiate |
| templates or use macros or inline functions from this file, or you compile |
| this file and link it with other files to produce an executable, this |
| file does not by itself cause the resulting executable to be covered by |
| the GNU General Public License. This exception does not however |
| invalidate any other reasons why the executable file might be covered by |
| the GNU General Public License. |
| */ |
| |
| #define __TBB_COUNT_TASK_NODES 1 |
| #include "harness_inject_scheduler.h" |
| |
| #if __TBB_TASK_GROUP_CONTEXT |
| |
| #define __TBB_ATOMICS_CODEGEN_BROKEN __SUNPRO_CC |
| |
| #include "tbb/task_scheduler_init.h" |
| #include "tbb/spin_mutex.h" |
| #include "tbb/tick_count.h" |
| |
| #if !TBB_USE_EXCEPTIONS && _MSC_VER |
| // Suppress "C++ exception handler used, but unwind semantics are not enabled" warning in STL headers |
| #pragma warning (push) |
| #pragma warning (disable: 4530) |
| #endif |
| |
| #include <string> |
| |
| #if !TBB_USE_EXCEPTIONS && _MSC_VER |
| #pragma warning (pop) |
| #endif |
| |
| #define NUM_CHILD_TASKS 256 |
| #define NUM_ROOT_TASKS 32 |
| #define NUM_ROOTS_IN_GROUP 8 |
| |
| //! Statistics about number of tasks in different states |
| class TaskStats { |
| typedef tbb::spin_mutex::scoped_lock lock_t; |
| //! Number of tasks allocated that was ever allocated |
| volatile intptr_t m_Existed; |
| //! Number of tasks executed to the moment |
| volatile intptr_t m_Executed; |
| //! Number of tasks allocated but not yet destroyed to the moment |
| volatile intptr_t m_Existing; |
| |
| mutable tbb::spin_mutex m_Mutex; |
| public: |
| //! Assumes that assignment is noncontended for the left-hand operand |
| const TaskStats& operator= ( const TaskStats& rhs ) { |
| if ( this != &rhs ) { |
| lock_t lock(rhs.m_Mutex); |
| m_Existed = rhs.m_Existed; |
| m_Executed = rhs.m_Executed; |
| m_Existing = rhs.m_Existing; |
| } |
| return *this; |
| } |
| intptr_t Existed() const { return m_Existed; } |
| intptr_t Executed() const { return m_Executed; } |
| intptr_t Existing() const { return m_Existing; } |
| void IncExisted() { lock_t lock(m_Mutex); ++m_Existed; ++m_Existing; } |
| void IncExecuted() { lock_t lock(m_Mutex); ++m_Executed; } |
| void DecExisting() { lock_t lock(m_Mutex); --m_Existing; } |
| //! Assumed to be used in uncontended manner only |
| void Reset() { m_Executed = m_Existing = m_Existed = 0; } |
| }; |
| |
| TaskStats g_CurStat; |
| |
| inline intptr_t Existed () { return g_CurStat.Existed(); } |
| |
| #include "harness_eh.h" |
| |
| bool g_BoostExecutedCount = true; |
| volatile bool g_TaskWasCancelled = false; |
| |
| inline void ResetGlobals () { |
| ResetEhGlobals(); |
| g_BoostExecutedCount = true; |
| g_TaskWasCancelled = false; |
| g_CurStat.Reset(); |
| } |
| |
| #define ASSERT_TEST_POSTCOND() \ |
| ASSERT (g_CurStat.Existed() >= g_CurStat.Executed(), "Total number of tasks is less than executed"); \ |
| ASSERT (!g_CurStat.Existing(), "Not all task objects have been destroyed"); \ |
| ASSERT (!tbb::task::self().is_cancelled(), "Scheduler's default context has not been cleaned up properly"); |
| |
| inline void WaitForException () { |
| int n = 0; |
| while ( ++n < c_Timeout && !__TBB_load_with_acquire(g_ExceptionCaught) ) |
| __TBB_Yield(); |
| ASSERT_WARNING( n < c_Timeout, "WaitForException failed" ); |
| } |
| |
| class TaskBase : public tbb::task { |
| tbb::task* execute () { |
| tbb::task* t = NULL; |
| __TBB_TRY { |
| t = do_execute(); |
| } __TBB_CATCH( ... ) { |
| g_CurStat.IncExecuted(); |
| __TBB_RETHROW(); |
| } |
| g_CurStat.IncExecuted(); |
| return t; |
| } |
| protected: |
| TaskBase ( bool throwException = true ) : m_Throw(throwException) { g_CurStat.IncExisted(); } |
| ~TaskBase () { g_CurStat.DecExisting(); } |
| |
| virtual tbb::task* do_execute () = 0; |
| |
| bool m_Throw; |
| }; // class TaskBase |
| |
| class LeafTask : public TaskBase { |
| tbb::task* do_execute () { |
| Harness::ConcurrencyTracker ct; |
| WaitUntilConcurrencyPeaks(); |
| if ( g_BoostExecutedCount ) |
| ++g_CurExecuted; |
| if ( m_Throw ) |
| ThrowTestException(NUM_CHILD_TASKS/2); |
| if ( !g_ThrowException ) |
| __TBB_Yield(); |
| return NULL; |
| } |
| public: |
| LeafTask ( bool throw_exception = true ) : TaskBase(throw_exception) {} |
| }; |
| |
| class SimpleRootTask : public TaskBase { |
| tbb::task* do_execute () { |
| set_ref_count(NUM_CHILD_TASKS + 1); |
| for ( size_t i = 0; i < NUM_CHILD_TASKS; ++i ) |
| spawn( *new( allocate_child() ) LeafTask(m_Throw) ); |
| wait_for_all(); |
| return NULL; |
| } |
| public: |
| SimpleRootTask ( bool throw_exception = true ) : TaskBase(throw_exception) {} |
| }; |
| |
| #if TBB_USE_EXCEPTIONS |
| |
| class SimpleThrowingTask : public tbb::task { |
| public: |
| tbb::task* execute () { throw 0; } |
| ~SimpleThrowingTask() {} |
| }; |
| |
| //! Checks if innermost running task information is updated correctly during cancellation processing |
| void Test0 () { |
| tbb::task_scheduler_init init (1); |
| tbb::empty_task &r = *new( tbb::task::allocate_root() ) tbb::empty_task; |
| tbb::task_list tl; |
| tl.push_back( *new( r.allocate_child() ) SimpleThrowingTask ); |
| tl.push_back( *new( r.allocate_child() ) SimpleThrowingTask ); |
| r.set_ref_count( 3 ); |
| try { |
| r.spawn_and_wait_for_all( tl ); |
| } |
| catch (...) {} |
| r.destroy( r ); |
| } |
| |
| //! Default exception behavior test. |
| /** Allocates a root task that spawns a bunch of children, one or several of which throw |
| a test exception in a worker or master thread (depending on the global setting). **/ |
| void Test1 () { |
| ResetGlobals(); |
| tbb::empty_task &r = *new( tbb::task::allocate_root() ) tbb::empty_task; |
| ASSERT (!g_CurStat.Existing() && !g_CurStat.Existed() && !g_CurStat.Executed(), |
| "something wrong with the task accounting"); |
| r.set_ref_count(NUM_CHILD_TASKS + 1); |
| for ( int i = 0; i < NUM_CHILD_TASKS; ++i ) |
| r.spawn( *new( r.allocate_child() ) LeafTask ); |
| TRY(); |
| r.wait_for_all(); |
| CATCH_AND_ASSERT(); |
| r.destroy(r); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test1 () |
| |
| //! Default exception behavior test. |
| /** Allocates and spawns root task that runs a bunch of children, one of which throws |
| a test exception in a worker thread. (Similar to Test1, except that the root task |
| is spawned by the test function, and children are created by the root task instead |
| of the test function body.) **/ |
| void Test2 () { |
| ResetGlobals(); |
| SimpleRootTask &r = *new( tbb::task::allocate_root() ) SimpleRootTask; |
| ASSERT (g_CurStat.Existing() == 1 && g_CurStat.Existed() == 1 && !g_CurStat.Executed(), |
| "something wrong with the task accounting"); |
| TRY(); |
| tbb::task::spawn_root_and_wait(r); |
| CATCH_AND_ASSERT(); |
| ASSERT (g_ExceptionCaught, "no exception occurred"); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test2 () |
| |
| //! The same as Test2() except the root task has explicit context. |
| /** The context is initialized as bound in order to check correctness of its associating |
| with a root task. **/ |
| void Test3 () { |
| ResetGlobals(); |
| tbb::task_group_context ctx(tbb::task_group_context::bound); |
| SimpleRootTask &r = *new( tbb::task::allocate_root(ctx) ) SimpleRootTask; |
| ASSERT (g_CurStat.Existing() == 1 && g_CurStat.Existed() == 1 && !g_CurStat.Executed(), |
| "something wrong with the task accounting"); |
| TRY(); |
| tbb::task::spawn_root_and_wait(r); |
| CATCH_AND_ASSERT(); |
| ASSERT (g_ExceptionCaught, "no exception occurred"); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test2 () |
| |
| class RootLauncherTask : public TaskBase { |
| tbb::task_group_context::kind_type m_CtxKind; |
| |
| tbb::task* do_execute () { |
| tbb::task_group_context ctx(m_CtxKind); |
| SimpleRootTask &r = *new( allocate_root(ctx) ) SimpleRootTask; |
| TRY(); |
| spawn_root_and_wait(r); |
| // Give a child of our siblings a chance to throw the test exception |
| WaitForException(); |
| CATCH(); |
| ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || !g_UnknownException, "unknown exception was caught"); |
| return NULL; |
| } |
| public: |
| RootLauncherTask ( tbb::task_group_context::kind_type ctx_kind = tbb::task_group_context::isolated ) : m_CtxKind(ctx_kind) {} |
| }; |
| |
| /** Allocates and spawns a bunch of roots, which allocate and spawn new root with |
| isolated context, which at last spawns a bunch of children each, one of which |
| throws a test exception in a worker thread. **/ |
| void Test4 () { |
| ResetGlobals(); |
| tbb::task_list tl; |
| for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i ) |
| tl.push_back( *new( tbb::task::allocate_root() ) RootLauncherTask ); |
| TRY(); |
| tbb::task::spawn_root_and_wait(tl); |
| CATCH_AND_ASSERT(); |
| ASSERT (!exceptionCaught, "exception in this scope is unexpected"); |
| intptr_t num_tasks_expected = NUM_ROOT_TASKS * (NUM_CHILD_TASKS + 2); |
| ASSERT (g_CurStat.Existed() == num_tasks_expected, "Wrong total number of tasks"); |
| if ( g_SolitaryException ) |
| ASSERT (g_CurStat.Executed() >= num_tasks_expected - NUM_CHILD_TASKS, "Unexpected number of executed tasks"); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test4 () |
| |
| class RootsGroupLauncherTask : public TaskBase { |
| tbb::task* do_execute () { |
| tbb::task_group_context ctx (tbb::task_group_context::isolated); |
| tbb::task_list tl; |
| for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i ) |
| tl.push_back( *new( allocate_root(ctx) ) SimpleRootTask ); |
| TRY(); |
| spawn_root_and_wait(tl); |
| // Give worker a chance to throw exception |
| WaitForException(); |
| CATCH_AND_ASSERT(); |
| return NULL; |
| } |
| }; |
| |
| /** Allocates and spawns a bunch of roots, which allocate and spawn groups of roots |
| with an isolated context shared by all group members, which at last spawn a bunch |
| of children each, one of which throws a test exception in a worker thread. **/ |
| void Test5 () { |
| ResetGlobals(); |
| tbb::task_list tl; |
| for ( size_t i = 0; i < NUM_ROOTS_IN_GROUP; ++i ) |
| tl.push_back( *new( tbb::task::allocate_root() ) RootsGroupLauncherTask ); |
| TRY(); |
| tbb::task::spawn_root_and_wait(tl); |
| CATCH_AND_ASSERT(); |
| ASSERT (!exceptionCaught, "unexpected exception intercepted"); |
| if ( g_SolitaryException ) { |
| intptr_t num_tasks_expected = NUM_ROOTS_IN_GROUP * (1 + NUM_ROOT_TASKS * (1 + NUM_CHILD_TASKS)); |
| intptr_t min_num_tasks_executed = num_tasks_expected - NUM_ROOT_TASKS * (NUM_CHILD_TASKS + 1); |
| ASSERT (g_CurStat.Executed() >= min_num_tasks_executed, "Too few tasks executed"); |
| } |
| ASSERT_TEST_POSTCOND(); |
| } // void Test5 () |
| |
| class ThrowingRootLauncherTask : public TaskBase { |
| tbb::task* do_execute () { |
| tbb::task_group_context ctx (tbb::task_group_context::bound); |
| SimpleRootTask &r = *new( allocate_root(ctx) ) SimpleRootTask(false); |
| TRY(); |
| spawn_root_and_wait(r); |
| CATCH(); |
| ASSERT (!exceptionCaught, "unexpected exception intercepted"); |
| ThrowTestException(NUM_CHILD_TASKS); |
| g_TaskWasCancelled |= is_cancelled(); |
| return NULL; |
| } |
| }; |
| |
| class BoundHierarchyLauncherTask : public TaskBase { |
| bool m_Recover; |
| |
| void alloc_roots ( tbb::task_group_context& ctx, tbb::task_list& tl ) { |
| for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i ) |
| tl.push_back( *new( allocate_root(ctx) ) ThrowingRootLauncherTask ); |
| } |
| |
| tbb::task* do_execute () { |
| tbb::task_group_context ctx (tbb::task_group_context::isolated); |
| tbb::task_list tl; |
| alloc_roots(ctx, tl); |
| TRY(); |
| spawn_root_and_wait(tl); |
| CATCH_AND_ASSERT(); |
| ASSERT (exceptionCaught, "no exception occurred"); |
| ASSERT (!tl.empty(), "task list was cleared somehow"); |
| if ( g_SolitaryException ) |
| ASSERT (g_TaskWasCancelled, "No tasks were cancelled despite of exception"); |
| if ( m_Recover ) { |
| // Test task_group_context::unbind and task_group_context::reset methods |
| g_ThrowException = false; |
| exceptionCaught = false; |
| tl.clear(); |
| alloc_roots(ctx, tl); |
| ctx.reset(); |
| try { |
| spawn_root_and_wait(tl); |
| } |
| catch (...) { |
| exceptionCaught = true; |
| } |
| ASSERT (!exceptionCaught, "unexpected exception occurred"); |
| } |
| return NULL; |
| } |
| public: |
| BoundHierarchyLauncherTask ( bool recover = false ) : m_Recover(recover) {} |
| |
| }; // class BoundHierarchyLauncherTask |
| |
| //! Test for bound contexts forming 2 level tree. Exception is thrown on the 1st (root) level. |
| /** Allocates and spawns a root that spawns a bunch of 2nd level roots sharing |
| the same isolated context, each of which in their turn spawns a single 3rd level |
| root with the bound context, and these 3rd level roots spawn bunches of leaves |
| in the end. Leaves do not generate exceptions. The test exception is generated |
| by one of the 2nd level roots. **/ |
| void Test6 () { |
| ResetGlobals(); |
| BoundHierarchyLauncherTask &r = *new( tbb::task::allocate_root() ) BoundHierarchyLauncherTask; |
| TRY(); |
| tbb::task::spawn_root_and_wait(r); |
| CATCH_AND_ASSERT(); |
| ASSERT (!exceptionCaught, "unexpected exception intercepted"); |
| // After the first of the branches (ThrowingRootLauncherTask) completes, |
| // the rest of the task tree may be collapsed before having a chance to execute leaves. |
| // A number of branches running concurrently with the first one will be able to spawn leaves though. |
| /// \todo: If additional checkpoints are added to scheduler the following assertion must weaken |
| intptr_t num_tasks_expected = 1 + NUM_ROOT_TASKS * (2 + NUM_CHILD_TASKS); |
| intptr_t min_num_tasks_created = 1 + g_NumThreads * 2 + NUM_CHILD_TASKS; |
| // 2 stands for BoundHierarchyLauncherTask and SimpleRootTask |
| // 1 corresponds to BoundHierarchyLauncherTask |
| intptr_t min_num_tasks_executed = 2 + 1 + NUM_CHILD_TASKS; |
| ASSERT (g_CurStat.Existed() <= num_tasks_expected, "Number of expected tasks is calculated incorrectly"); |
| ASSERT (g_CurStat.Existed() >= min_num_tasks_created, "Too few tasks created"); |
| ASSERT (g_CurStat.Executed() >= min_num_tasks_executed, "Too few tasks executed"); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test6 () |
| |
| //! Tests task_group_context::unbind and task_group_context::reset methods. |
| /** Allocates and spawns a root that spawns a bunch of 2nd level roots sharing |
| the same isolated context, each of which in their turn spawns a single 3rd level |
| root with the bound context, and these 3rd level roots spawn bunches of leaves |
| in the end. Leaves do not generate exceptions. The test exception is generated |
| by one of the 2nd level roots. **/ |
| void Test7 () { |
| ResetGlobals(); |
| BoundHierarchyLauncherTask &r = *new( tbb::task::allocate_root() ) BoundHierarchyLauncherTask; |
| TRY(); |
| tbb::task::spawn_root_and_wait(r); |
| CATCH_AND_ASSERT(); |
| ASSERT (!exceptionCaught, "unexpected exception intercepted"); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test6 () |
| |
| class BoundHierarchyLauncherTask2 : public TaskBase { |
| tbb::task* do_execute () { |
| tbb::task_group_context ctx; |
| tbb::task_list tl; |
| for ( size_t i = 0; i < NUM_ROOT_TASKS; ++i ) |
| tl.push_back( *new( allocate_root(ctx) ) RootLauncherTask(tbb::task_group_context::bound) ); |
| TRY(); |
| spawn_root_and_wait(tl); |
| CATCH_AND_ASSERT(); |
| // Exception must be intercepted by RootLauncherTask |
| ASSERT (!exceptionCaught, "no exception occurred"); |
| return NULL; |
| } |
| }; // class BoundHierarchyLauncherTask2 |
| |
| //! Test for bound contexts forming 2 level tree. Exception is thrown in the 2nd (outer) level. |
| /** Allocates and spawns a root that spawns a bunch of 2nd level roots sharing |
| the same isolated context, each of which in their turn spawns a single 3rd level |
| root with the bound context, and these 3rd level roots spawn bunches of leaves |
| in the end. The test exception is generated by one of the leaves. **/ |
| void Test8 () { |
| ResetGlobals(); |
| BoundHierarchyLauncherTask2 &r = *new( tbb::task::allocate_root() ) BoundHierarchyLauncherTask2; |
| TRY(); |
| tbb::task::spawn_root_and_wait(r); |
| CATCH_AND_ASSERT(); |
| ASSERT (!exceptionCaught, "unexpected exception intercepted"); |
| if ( g_SolitaryException ) { |
| intptr_t num_tasks_expected = 1 + NUM_ROOT_TASKS * (2 + NUM_CHILD_TASKS); |
| intptr_t min_num_tasks_created = 1 + g_NumThreads * (2 + NUM_CHILD_TASKS); |
| intptr_t min_num_tasks_executed = num_tasks_expected - (NUM_CHILD_TASKS + 1); |
| ASSERT (g_CurStat.Existed() <= num_tasks_expected, "Number of expected tasks is calculated incorrectly"); |
| ASSERT (g_CurStat.Existed() >= min_num_tasks_created, "Too few tasks created"); |
| ASSERT (g_CurStat.Executed() >= min_num_tasks_executed, "Too few tasks executed"); |
| } |
| ASSERT_TEST_POSTCOND(); |
| } // void Test8 () |
| |
| template<typename T> |
| void ThrowMovableException ( intptr_t threshold, const T& data ) { |
| if ( !IsThrowingThread() ) |
| return; |
| if ( !g_SolitaryException ) { |
| #if __TBB_ATOMICS_CODEGEN_BROKEN |
| g_ExceptionsThrown = g_ExceptionsThrown + 1; |
| #else |
| ++g_ExceptionsThrown; |
| #endif |
| throw tbb::movable_exception<T>(data); |
| } |
| while ( g_CurStat.Existed() < threshold ) |
| __TBB_Yield(); |
| if ( g_ExceptionsThrown.compare_and_swap(1, 0) == 0 ) |
| throw tbb::movable_exception<T>(data); |
| } |
| |
| const int g_IntExceptionData = -375; |
| const std::string g_StringExceptionData = "My test string"; |
| |
| // Exception data class implementing minimal requirements of tbb::movable_exception |
| class ExceptionData { |
| const ExceptionData& operator = ( const ExceptionData& src ); |
| explicit ExceptionData ( int n ) : m_Int(n), m_String(g_StringExceptionData) {} |
| public: |
| ExceptionData ( const ExceptionData& src ) : m_Int(src.m_Int), m_String(src.m_String) {} |
| ~ExceptionData () {} |
| |
| int m_Int; |
| std::string m_String; |
| |
| // Simple way to provide an instance when all initializing constructors are private |
| // and to avoid memory reclamation problems. |
| static ExceptionData s_data; |
| }; |
| |
| ExceptionData ExceptionData::s_data(g_IntExceptionData); |
| |
| typedef tbb::movable_exception<int> SolitaryMovableException; |
| typedef tbb::movable_exception<ExceptionData> MultipleMovableException; |
| |
| class LeafTaskWithMovableExceptions : public TaskBase { |
| bool m_IntAsData; |
| |
| tbb::task* do_execute () { |
| Harness::ConcurrencyTracker ct; |
| WaitUntilConcurrencyPeaks(); |
| if ( g_SolitaryException ) |
| ThrowMovableException<int>(NUM_CHILD_TASKS/2, g_IntExceptionData); |
| else |
| ThrowMovableException<ExceptionData>(NUM_CHILD_TASKS/2, ExceptionData::s_data); |
| return NULL; |
| } |
| }; |
| |
| void CheckException ( tbb::tbb_exception& e ) { |
| ASSERT (strcmp(e.name(), (g_SolitaryException ? typeid(SolitaryMovableException) |
| : typeid(MultipleMovableException)).name() ) == 0, |
| "Unexpected original exception name"); |
| ASSERT (strcmp(e.what(), "tbb::movable_exception") == 0, "Unexpected original exception info "); |
| if ( g_SolitaryException ) { |
| SolitaryMovableException& me = dynamic_cast<SolitaryMovableException&>(e); |
| ASSERT (me.data() == g_IntExceptionData, "Unexpected solitary movable_exception data"); |
| } |
| else { |
| MultipleMovableException& me = dynamic_cast<MultipleMovableException&>(e); |
| ASSERT (me.data().m_Int == g_IntExceptionData, "Unexpected multiple movable_exception int data"); |
| ASSERT (me.data().m_String == g_StringExceptionData, "Unexpected multiple movable_exception string data"); |
| } |
| } |
| |
| void CheckException () { |
| try { |
| throw; |
| } catch ( tbb::tbb_exception& e ) { |
| CheckException(e); |
| } |
| catch ( ... ) { |
| } |
| } |
| |
| //! Test for movable_exception behavior, and external exception recording. |
| /** Allocates a root task that spawns a bunch of children, one or several of which throw |
| a movable exception in a worker or master thread (depending on the global settings). |
| The test also checks the correctness of multiple rethrowing of the pending exception. **/ |
| void TestMovableException () { |
| ResetGlobals(); |
| tbb::task_group_context ctx; |
| tbb::empty_task *r = new( tbb::task::allocate_root() ) tbb::empty_task; |
| ASSERT (!g_CurStat.Existing() && !g_CurStat.Existed() && !g_CurStat.Executed(), |
| "something wrong with the task accounting"); |
| r->set_ref_count(NUM_CHILD_TASKS + 1); |
| for ( int i = 0; i < NUM_CHILD_TASKS; ++i ) |
| r->spawn( *new( r->allocate_child() ) LeafTaskWithMovableExceptions ); |
| TRY() |
| r->wait_for_all(); |
| } catch ( ... ) { |
| ASSERT (!ctx.is_group_execution_cancelled(), ""); |
| CheckException(); |
| try { |
| throw; |
| } catch ( tbb::tbb_exception& e ) { |
| CheckException(e); |
| g_ExceptionCaught = exceptionCaught = true; |
| } |
| catch ( ... ) { |
| g_ExceptionCaught = true; |
| g_UnknownException = unknownException = true; |
| } |
| ctx.register_pending_exception(); |
| ASSERT (ctx.is_group_execution_cancelled(), "After exception registration the context must be in the cancelled state"); |
| } |
| r->destroy(*r); |
| ASSERT_EXCEPTION(); |
| ASSERT_TEST_POSTCOND(); |
| |
| r = new( tbb::task::allocate_root(ctx) ) tbb::empty_task; |
| r->set_ref_count(1); |
| g_ExceptionCaught = g_UnknownException = false; |
| try { |
| r->wait_for_all(); |
| } catch ( tbb::tbb_exception& e ) { |
| CheckException(e); |
| g_ExceptionCaught = true; |
| } |
| catch ( ... ) { |
| g_ExceptionCaught = true; |
| g_UnknownException = true; |
| } |
| ASSERT (g_ExceptionCaught, "no exception occurred"); |
| ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || !g_UnknownException, "unknown exception was caught"); |
| r->destroy(*r); |
| } // void Test10 () |
| |
| #endif /* TBB_USE_EXCEPTIONS */ |
| |
| template<class T> |
| class CtxLauncherTask : public tbb::task { |
| tbb::task_group_context &m_Ctx; |
| |
| tbb::task* execute () { |
| tbb::task::spawn_root_and_wait( *new( tbb::task::allocate_root(m_Ctx) ) T ); |
| return NULL; |
| } |
| public: |
| CtxLauncherTask ( tbb::task_group_context& ctx ) : m_Ctx(ctx) {} |
| }; |
| |
| //! Test for cancelling a task hierarchy from outside (from a task running in parallel with it). |
| void TestCancelation () { |
| ResetGlobals(); |
| g_ThrowException = false; |
| tbb::task_group_context ctx; |
| tbb::task_list tl; |
| tl.push_back( *new( tbb::task::allocate_root() ) CtxLauncherTask<SimpleRootTask>(ctx) ); |
| tl.push_back( *new( tbb::task::allocate_root() ) CancellatorTask(ctx, NUM_CHILD_TASKS / 4) ); |
| TRY(); |
| tbb::task::spawn_root_and_wait(tl); |
| CATCH_AND_FAIL(); |
| ASSERT (g_CurStat.Executed() <= g_ExecutedAtCatch + g_NumThreads, "Too many tasks were executed after cancellation"); |
| ASSERT_TEST_POSTCOND(); |
| } // void Test9 () |
| |
| class CtxDestroyerTask : public tbb::task { |
| int m_nestingLevel; |
| |
| tbb::task* execute () { |
| ASSERT ( m_nestingLevel >= 0 && m_nestingLevel < MaxNestingDepth, "Wrong nesting level. The test is broken" ); |
| tbb::task_group_context ctx; |
| tbb::task *t = new( tbb::task::allocate_root(ctx) ) tbb::empty_task; |
| int level = ++m_nestingLevel; |
| if ( level < MaxNestingDepth ) { |
| execute(); |
| } |
| else { |
| if ( !CancellatorTask::WaitUntilReady() ) |
| REPORT( "Warning: missing wakeup\n" ); |
| ++g_CurExecuted; |
| } |
| if ( ctx.is_group_execution_cancelled() ) |
| ++s_numCancelled; |
| t->destroy(*t); |
| return NULL; |
| } |
| public: |
| CtxDestroyerTask () : m_nestingLevel(0) { s_numCancelled = 0; } |
| |
| static const int MaxNestingDepth = 256; |
| static int s_numCancelled; |
| }; |
| |
| int CtxDestroyerTask::s_numCancelled = 0; |
| |
| //! Test for data race between cancellation propagation and context destruction. |
| /** If the data race ever occurs, an assertion inside TBB will be triggered. **/ |
| void TestCtxDestruction () { |
| for ( size_t i = 0; i < 10; ++i ) { |
| tbb::task_group_context ctx; |
| tbb::task_list tl; |
| ResetGlobals(); |
| g_BoostExecutedCount = false; |
| g_ThrowException = false; |
| CancellatorTask::Reset(); |
| |
| tl.push_back( *new( tbb::task::allocate_root() ) CtxLauncherTask<CtxDestroyerTask>(ctx) ); |
| tl.push_back( *new( tbb::task::allocate_root() ) CancellatorTask(ctx, 1) ); |
| tbb::task::spawn_root_and_wait(tl); |
| ASSERT( g_CurExecuted == 1, "Test is broken" ); |
| ASSERT( CtxDestroyerTask::s_numCancelled <= CtxDestroyerTask::MaxNestingDepth, "Test is broken" ); |
| } |
| } // void TestCtxDestruction() |
| |
| #include <algorithm> |
| #include "harness_barrier.h" |
| |
| class CtxConcurrentDestroyer : NoAssign, Harness::NoAfterlife { |
| static const int ContextsPerThread = 512; |
| |
| static int s_Concurrency; |
| static int s_NumContexts; |
| static tbb::task_group_context** s_Contexts; |
| static char* s_Buffer; |
| static Harness::SpinBarrier s_Barrier; |
| static Harness::SpinBarrier s_ExitBarrier; |
| |
| struct Shuffler { |
| void operator() () const { std::random_shuffle(s_Contexts, s_Contexts + s_NumContexts); } |
| }; |
| public: |
| static void Init ( int p ) { |
| s_Concurrency = p; |
| s_NumContexts = p * ContextsPerThread; |
| s_Contexts = new tbb::task_group_context*[s_NumContexts]; |
| s_Buffer = new char[s_NumContexts * sizeof(tbb::task_group_context)]; |
| s_Barrier.initialize( p ); |
| s_ExitBarrier.initialize( p ); |
| } |
| static void Uninit () { |
| for ( int i = 0; i < s_NumContexts; ++i ) { |
| tbb::internal::context_list_node_t &node = s_Contexts[i]->my_node; |
| ASSERT( !node.my_next && !node.my_prev, "Destroyed context was written to during context chain update" ); |
| } |
| delete s_Contexts; |
| delete s_Buffer; |
| } |
| |
| void operator() ( int id ) const { |
| int begin = ContextsPerThread * id, |
| end = begin + ContextsPerThread; |
| for ( int i = begin; i < end; ++i ) |
| s_Contexts[i] = new( s_Buffer + i * sizeof(tbb::task_group_context) ) tbb::task_group_context; |
| s_Barrier.wait( Shuffler() ); |
| for ( int i = begin; i < end; ++i ) { |
| s_Contexts[i]->tbb::task_group_context::~task_group_context(); |
| memset( s_Contexts[i], 0, sizeof(tbb::task_group_context) ); |
| } |
| s_ExitBarrier.wait(); |
| } |
| }; // class CtxConcurrentDestroyer |
| |
| int CtxConcurrentDestroyer::s_Concurrency; |
| int CtxConcurrentDestroyer::s_NumContexts; |
| tbb::task_group_context** CtxConcurrentDestroyer::s_Contexts; |
| char* CtxConcurrentDestroyer::s_Buffer; |
| Harness::SpinBarrier CtxConcurrentDestroyer::s_Barrier; |
| Harness::SpinBarrier CtxConcurrentDestroyer::s_ExitBarrier; |
| |
| void TestConcurrentCtxDestruction () { |
| REMARK( "TestConcurrentCtxDestruction\n" ); |
| CtxConcurrentDestroyer::Init(g_NumThreads); |
| NativeParallelFor( g_NumThreads, CtxConcurrentDestroyer() ); |
| CtxConcurrentDestroyer::Uninit(); |
| } |
| |
| void RunTests () { |
| REMARK ("Number of threads %d\n", g_NumThreads); |
| tbb::task_scheduler_init init (g_NumThreads); |
| g_Master = Harness::CurrentTid(); |
| #if TBB_USE_EXCEPTIONS |
| Test1(); |
| Test2(); |
| Test3(); |
| Test4(); |
| Test5(); |
| Test6(); |
| Test7(); |
| Test8(); |
| TestMovableException(); |
| #endif /* TBB_USE_EXCEPTIONS */ |
| TestCancelation(); |
| TestCtxDestruction(); |
| #if !RML_USE_WCRM |
| TestConcurrentCtxDestruction(); |
| #endif |
| } |
| #endif /* __TBB_TASK_GROUP_CONTEXT */ |
| |
| int TestMain () { |
| REMARK ("Using %s", TBB_USE_CAPTURED_EXCEPTION ? "tbb:captured_exception" : "exact exception propagation"); |
| MinThread = min(NUM_ROOTS_IN_GROUP, min(tbb::task_scheduler_init::default_num_threads(), max(2, MinThread))); |
| MaxThread = min(NUM_ROOTS_IN_GROUP, max(MinThread, min(tbb::task_scheduler_init::default_num_threads(), MaxThread))); |
| ASSERT (NUM_ROOTS_IN_GROUP < NUM_ROOT_TASKS, "Fix defines"); |
| #if __TBB_TASK_GROUP_CONTEXT |
| #if TBB_USE_EXCEPTIONS |
| // Test0 always runs on one thread |
| Test0(); |
| #endif /* TBB_USE_EXCEPTIONS */ |
| g_SolitaryException = 0; |
| for ( g_NumThreads = MinThread; g_NumThreads <= MaxThread; ++g_NumThreads ) |
| RunTests(); |
| return Harness::Done; |
| #else |
| return Harness::Skipped; |
| #endif /* __TBB_TASK_GROUP_CONTEXT */ |
| } |