blob: 44ed1f3056776a11ab976c1dd7a28281040b86be [file] [log] [blame]
/*
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 */
}