| /* |
| 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. |
| */ |
| |
| // Testing automatic initialization of TBB task scheduler, so do not use task_scheduler_init anywhere. |
| |
| #include "tbb/task.h" |
| |
| #define HARNESS_NO_PARSE_COMMAND_LINE 1 |
| #include "harness.h" |
| #include "tbb/atomic.h" |
| |
| static tbb::atomic<int> g_NumTestsExecuted; |
| |
| #define TEST_PROLOGUE() ++g_NumTestsExecuted |
| |
| // Global data used in testing use cases with cross-thread usage of TBB objects |
| static tbb::task *g_Root1 = NULL, |
| *g_Root2 = NULL, |
| *g_Root3 = NULL, |
| *g_Task = NULL; |
| static tbb::task_group_context* g_Ctx = NULL; |
| |
| |
| void TestTaskSelf () { |
| TEST_PROLOGUE(); |
| tbb::task& t = tbb::task::self(); |
| ASSERT( !t.parent() && t.ref_count() == 1 && !t.affinity(), "Master's default task properties changed?" ); |
| } |
| |
| void TestRootAllocation () { |
| TEST_PROLOGUE(); |
| tbb::task &r = *new( tbb::task::allocate_root() ) tbb::empty_task; |
| tbb::task::spawn_root_and_wait(r); |
| } |
| |
| inline void ExecuteChildAndCleanup ( tbb::task &r, tbb::task &t ) { |
| r.set_ref_count(2); |
| r.spawn_and_wait_for_all(t); |
| r.destroy(r); |
| } |
| |
| void TestChildAllocation () { |
| TEST_PROLOGUE(); |
| tbb::task &t = *new( g_Root1->allocate_child() ) tbb::empty_task; |
| ExecuteChildAndCleanup( *g_Root1, t ); |
| } |
| |
| void TestAdditionalChildAllocation () { |
| TEST_PROLOGUE(); |
| tbb::task &t = *new( g_Root2->allocate_additional_child_of(*g_Root2) ) tbb::empty_task; |
| ExecuteChildAndCleanup( *g_Root2, t ); |
| } |
| |
| void TestTaskGroupContextCreation () { |
| TEST_PROLOGUE(); |
| tbb::task_group_context ctx; |
| tbb::task &r = *new( tbb::task::allocate_root(ctx) ) tbb::empty_task; |
| tbb::task::spawn_root_and_wait(r); |
| } |
| |
| void TestRootAllocationWithContext () { |
| TEST_PROLOGUE(); |
| tbb::task* root = new( tbb::task::allocate_root(*g_Ctx) ) tbb::empty_task; |
| tbb::task::spawn_root_and_wait(*root); |
| } |
| |
| void TestSpawn () { |
| TEST_PROLOGUE(); |
| g_Task->spawn(*g_Task); |
| } |
| |
| void TestWaitForAll () { |
| TEST_PROLOGUE(); |
| g_Root3->wait_for_all(); |
| g_Root3->destroy( *g_Root3 ); |
| } |
| |
| typedef void (*TestFnPtr)(); |
| |
| const TestFnPtr TestFuncsTable[] = { |
| TestTaskSelf, TestRootAllocation, TestChildAllocation, TestAdditionalChildAllocation, |
| TestTaskGroupContextCreation, TestRootAllocationWithContext, TestSpawn, TestWaitForAll }; |
| |
| const int NumTestFuncs = sizeof(TestFuncsTable) / sizeof(TestFnPtr); |
| |
| struct TestThreadBody : NoAssign, Harness::NoAfterlife { |
| // Each invocation of operator() happens in a fresh thread with zero-based ID |
| // id, and checks a specific auto-initialization scenario. |
| void operator() ( int id ) const { |
| ASSERT( id >= 0 && id < NumTestFuncs, "Test diver: NativeParallelFor is used incorrectly" ); |
| TestFuncsTable[id](); |
| } |
| }; |
| |
| |
| #include "../tbb/tls.h" |
| |
| void UseAFewNewTlsKeys () { |
| tbb::internal::tls<intptr_t> tls1, tls2, tls3, tls4; |
| tls1 = tls2 = tls3 = tls4 = -1; |
| } |
| |
| using tbb::internal::spin_wait_until_eq; |
| |
| volatile bool FafStarted = false, |
| FafCanFinish = false, |
| FafCompleted = false; |
| |
| //! This task is supposed to be executed during termination of an auto-initialized master thread |
| class FireAndForgetTask : public tbb::task { |
| tbb::task* execute () { |
| // Let another master thread proceed requesting new TLS keys |
| FafStarted = true; |
| UseAFewNewTlsKeys(); |
| // Wait while another master thread dirtied its new TLS slots |
| spin_wait_until_eq( FafCanFinish, true ); |
| FafCompleted = true; |
| return NULL; |
| } |
| public: // to make gcc 3.2.3 happy |
| ~FireAndForgetTask() { |
| ASSERT(FafCompleted, "FireAndForgetTask got erroneously cancelled?"); |
| } |
| }; |
| |
| #include "harness_barrier.h" |
| Harness::SpinBarrier driver_barrier(2); |
| |
| struct DriverThreadBody : NoAssign, Harness::NoAfterlife { |
| void operator() ( int id ) const { |
| ASSERT( id < 2, "Only two test driver threads are expected" ); |
| // a barrier is required to ensure both threads started; otherwise the test may deadlock: |
| // the first thread would execute FireAndForgetTask at shutdown and wait for FafCanFinish, |
| // while the second thread wouldn't even start waiting for the loader lock hold by the first one. |
| if ( id == 0 ) { |
| driver_barrier.wait(); |
| // Prepare global data |
| g_Root1 = new( tbb::task::allocate_root() ) tbb::empty_task; |
| g_Root2 = new( tbb::task::allocate_root() ) tbb::empty_task; |
| g_Root3 = new( tbb::task::allocate_root() ) tbb::empty_task; |
| g_Task = new( g_Root3->allocate_child() ) tbb::empty_task; |
| g_Root3->set_ref_count(2); |
| // Run tests |
| NativeParallelFor( NumTestFuncs, TestThreadBody() ); |
| ASSERT( g_NumTestsExecuted == NumTestFuncs, "Test driver: Wrong number of tests executed" ); |
| |
| // This test checks the validity of temporarily restoring the value of |
| // the last TLS slot for a given key during the termination of an |
| // auto-initialized master thread (in governor::auto_terminate). |
| // If anything goes wrong, generic_scheduler::cleanup_master() will assert. |
| // The context for this task must be valid till the task completion. |
| tbb::task &r = *new( tbb::task::allocate_root(*g_Ctx) ) FireAndForgetTask; |
| r.spawn(r); |
| } |
| else { |
| tbb::task_group_context ctx; |
| g_Ctx = &ctx; |
| driver_barrier.wait(); |
| spin_wait_until_eq( FafStarted, true ); |
| UseAFewNewTlsKeys(); |
| FafCanFinish = true; |
| spin_wait_until_eq( FafCompleted, true ); |
| } |
| } |
| }; |
| |
| int TestMain () { |
| // Do not use any TBB functionality in the main thread! |
| NativeParallelFor( 2, DriverThreadBody() ); |
| return Harness::Done; |
| } |