You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
12 KiB
327 lines
12 KiB
/*
|
|
Copyright 2005-2013 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.
|
|
*/
|
|
|
|
#include <typeinfo>
|
|
#include "tbb/tbb_exception.h"
|
|
#include "tbb/atomic.h"
|
|
#if USE_TASK_SCHEDULER_OBSERVER
|
|
#include "tbb/task_scheduler_observer.h"
|
|
#endif
|
|
#include "harness.h"
|
|
#include "harness_concurrency_tracker.h"
|
|
|
|
int g_NumThreads = 0;
|
|
Harness::tid_t g_Master = 0;
|
|
const char * g_Orig_Wakeup_Msg = "Missed wakeup or machine is overloaded?";
|
|
const char * g_Wakeup_Msg = g_Orig_Wakeup_Msg;
|
|
|
|
tbb::atomic<intptr_t> g_CurExecuted,
|
|
g_ExecutedAtLastCatch,
|
|
g_ExecutedAtFirstCatch,
|
|
g_ExceptionsThrown,
|
|
g_MasterExecutedThrow, // number of times master entered exception code
|
|
g_NonMasterExecutedThrow, // number of times nonmaster entered exception code
|
|
g_PipelinesStarted;
|
|
volatile bool g_ExceptionCaught = false,
|
|
g_UnknownException = false;
|
|
|
|
#if USE_TASK_SCHEDULER_OBSERVER
|
|
tbb::atomic<intptr_t> g_ActualMaxThreads;
|
|
tbb::atomic<intptr_t> g_ActualCurrentThreads;
|
|
#endif
|
|
|
|
volatile bool g_ThrowException = true,
|
|
// g_Flog is true for nested construct tests with catches (exceptions are not allowed to
|
|
// propagate to the tbb construct itself.)
|
|
g_Flog = false,
|
|
g_MasterExecuted = false,
|
|
g_NonMasterExecuted = false;
|
|
|
|
bool g_ExceptionInMaster = false;
|
|
bool g_SolitaryException = false;
|
|
bool g_NestedPipelines = false;
|
|
|
|
//! Number of exceptions propagated into the user code (i.e. intercepted by the tests)
|
|
tbb::atomic<intptr_t> g_NumExceptionsCaught;
|
|
|
|
//-----------------------------------------------------------
|
|
|
|
#if USE_TASK_SCHEDULER_OBSERVER
|
|
class eh_test_observer : public tbb::task_scheduler_observer {
|
|
public:
|
|
/*override*/
|
|
void on_scheduler_entry(bool is_worker) {
|
|
if(is_worker) { // we've already counted the master
|
|
size_t p = ++g_ActualCurrentThreads;
|
|
size_t q = g_ActualMaxThreads;
|
|
while(q < p) {
|
|
q = g_ActualMaxThreads.compare_and_swap(p,q);
|
|
}
|
|
}
|
|
else {
|
|
// size_t q = g_ActualMaxThreads;
|
|
}
|
|
}
|
|
/*override*/
|
|
void on_scheduler_exit(bool is_worker) {
|
|
if(is_worker) {
|
|
--g_ActualCurrentThreads;
|
|
}
|
|
}
|
|
};
|
|
#endif
|
|
//-----------------------------------------------------------
|
|
|
|
inline void ResetEhGlobals ( bool throwException = true, bool flog = false ) {
|
|
Harness::ConcurrencyTracker::Reset();
|
|
g_CurExecuted = g_ExecutedAtLastCatch = g_ExecutedAtFirstCatch = 0;
|
|
g_ExceptionCaught = false;
|
|
g_UnknownException = false;
|
|
g_NestedPipelines = false;
|
|
g_ThrowException = throwException;
|
|
g_MasterExecutedThrow = 0;
|
|
g_NonMasterExecutedThrow = 0;
|
|
g_Flog = flog;
|
|
g_MasterExecuted = false;
|
|
g_NonMasterExecuted = false;
|
|
#if USE_TASK_SCHEDULER_OBSERVER
|
|
g_ActualMaxThreads = 1; // count master
|
|
g_ActualCurrentThreads = 1; // count master
|
|
#endif
|
|
g_ExceptionsThrown = g_NumExceptionsCaught = g_PipelinesStarted = 0;
|
|
}
|
|
|
|
#if TBB_USE_EXCEPTIONS
|
|
class test_exception : public std::exception {
|
|
const char* my_description;
|
|
public:
|
|
test_exception ( const char* description ) : my_description(description) {}
|
|
|
|
const char* what() const throw() { return my_description; }
|
|
};
|
|
|
|
class solitary_test_exception : public test_exception {
|
|
public:
|
|
solitary_test_exception ( const char* description ) : test_exception(description) {}
|
|
};
|
|
|
|
#if TBB_USE_CAPTURED_EXCEPTION
|
|
typedef tbb::captured_exception PropagatedException;
|
|
#define EXCEPTION_NAME(e) e.name()
|
|
#else
|
|
typedef test_exception PropagatedException;
|
|
#define EXCEPTION_NAME(e) typeid(e).name()
|
|
#endif
|
|
|
|
#define EXCEPTION_DESCR "Test exception"
|
|
|
|
#if HARNESS_EH_SIMPLE_MODE
|
|
|
|
static void ThrowTestException () {
|
|
++g_ExceptionsThrown;
|
|
throw test_exception(EXCEPTION_DESCR);
|
|
}
|
|
|
|
#else /* !HARNESS_EH_SIMPLE_MODE */
|
|
|
|
static void ThrowTestException ( intptr_t threshold ) {
|
|
bool inMaster = (Harness::CurrentTid() == g_Master);
|
|
if ( !g_ThrowException || // if we're not supposed to throw
|
|
(!g_Flog && // if we're not catching throw in bodies and
|
|
(g_ExceptionInMaster ^ inMaster)) ) { // we're the master and not expected to throw
|
|
// or are the master and the master is not the one to throw (??)
|
|
return;
|
|
}
|
|
while ( Existed() < threshold )
|
|
__TBB_Yield();
|
|
if ( !g_SolitaryException ) {
|
|
++g_ExceptionsThrown;
|
|
if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow;
|
|
throw test_exception(EXCEPTION_DESCR);
|
|
}
|
|
// g_SolitaryException == true
|
|
if(g_NestedPipelines) {
|
|
// only throw exception if we have started at least two inner pipelines
|
|
// else return
|
|
if(g_PipelinesStarted >= 3) {
|
|
if ( g_ExceptionsThrown.compare_and_swap(1, 0) == 0 ) {
|
|
if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow;
|
|
throw solitary_test_exception(EXCEPTION_DESCR);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ( g_ExceptionsThrown.compare_and_swap(1, 0) == 0 ) {
|
|
if(inMaster) ++g_MasterExecutedThrow; else ++g_NonMasterExecutedThrow;
|
|
throw solitary_test_exception(EXCEPTION_DESCR);
|
|
}
|
|
}
|
|
}
|
|
#endif /* !HARNESS_EH_SIMPLE_MODE */
|
|
|
|
#define UPDATE_COUNTS() \
|
|
{ \
|
|
++g_CurExecuted; \
|
|
if(g_Master == Harness::CurrentTid()) g_MasterExecuted = true; \
|
|
else g_NonMasterExecuted = true; \
|
|
if( tbb::task::self().is_cancelled() ) ++g_TGCCancelled; \
|
|
}
|
|
|
|
#define CATCH() \
|
|
} catch ( PropagatedException& e ) { \
|
|
g_ExecutedAtFirstCatch.compare_and_swap(g_CurExecuted,0); \
|
|
g_ExecutedAtLastCatch = g_CurExecuted; \
|
|
ASSERT( e.what(), "Empty what() string" ); \
|
|
ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(EXCEPTION_NAME(e), (g_SolitaryException ? typeid(solitary_test_exception) : typeid(test_exception)).name() ) == 0, "Unexpected original exception name"); \
|
|
ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || strcmp(e.what(), EXCEPTION_DESCR) == 0, "Unexpected original exception info"); \
|
|
g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \
|
|
++g_NumExceptionsCaught; \
|
|
} catch ( tbb::tbb_exception& e ) { \
|
|
REPORT("Unexpected %s\n", e.name()); \
|
|
ASSERT (g_UnknownException && !g_UnknownException, "Unexpected tbb::tbb_exception" ); \
|
|
} catch ( std::exception& e ) { \
|
|
REPORT("Unexpected %s\n", typeid(e).name()); \
|
|
ASSERT (g_UnknownException && !g_UnknownException, "Unexpected std::exception" ); \
|
|
} catch ( ... ) { \
|
|
g_ExceptionCaught = l_ExceptionCaughtAtCurrentLevel = true; \
|
|
g_UnknownException = unknownException = true; \
|
|
} \
|
|
if ( !g_SolitaryException ) \
|
|
REMARK_ONCE ("Multiple exceptions mode: %d throws", (intptr_t)g_ExceptionsThrown);
|
|
|
|
#define ASSERT_EXCEPTION() \
|
|
{ \
|
|
ASSERT (g_ExceptionsThrown ? g_ExceptionCaught : true, "throw without catch"); \
|
|
ASSERT (!g_ExceptionsThrown ? !g_ExceptionCaught : true, "catch without throw"); \
|
|
ASSERT (g_ExceptionCaught || (g_ExceptionInMaster && !g_MasterExecutedThrow) || (!g_ExceptionInMaster && !g_NonMasterExecutedThrow), "no exception occurred"); \
|
|
ASSERT (__TBB_EXCEPTION_TYPE_INFO_BROKEN || !g_UnknownException, "unknown exception was caught"); \
|
|
}
|
|
|
|
#define CATCH_AND_ASSERT() \
|
|
CATCH() \
|
|
ASSERT_EXCEPTION()
|
|
|
|
#else /* !TBB_USE_EXCEPTIONS */
|
|
|
|
inline void ThrowTestException ( intptr_t ) {}
|
|
|
|
#endif /* !TBB_USE_EXCEPTIONS */
|
|
|
|
#define TRY() \
|
|
bool l_ExceptionCaughtAtCurrentLevel = false, unknownException = false; \
|
|
__TBB_TRY {
|
|
|
|
// "l_ExceptionCaughtAtCurrentLevel || unknownException" is used only to "touch" otherwise unused local variables
|
|
#define CATCH_AND_FAIL() } __TBB_CATCH(...) { \
|
|
ASSERT (false, "Cancelling tasks must not cause any exceptions"); \
|
|
(void)(l_ExceptionCaughtAtCurrentLevel && unknownException); \
|
|
}
|
|
|
|
const int c_Timeout = 1000000;
|
|
|
|
void WaitUntilConcurrencyPeaks ( int expected_peak ) {
|
|
if ( g_Flog )
|
|
return;
|
|
int n = 0;
|
|
retry:
|
|
while ( ++n < c_Timeout && (int)Harness::ConcurrencyTracker::PeakParallelism() < expected_peak )
|
|
__TBB_Yield();
|
|
#if USE_TASK_SCHEDULER_OBSERVER
|
|
ASSERT_WARNING( g_NumThreads == g_ActualMaxThreads, "Library did not provide sufficient threads");
|
|
#endif
|
|
ASSERT_WARNING(n < c_Timeout,g_Wakeup_Msg);
|
|
// Workaround in case a missed wakeup takes place
|
|
if ( n == c_Timeout ) {
|
|
tbb::task &r = *new( tbb::task::allocate_root() ) tbb::empty_task();
|
|
r.spawn(r);
|
|
n = 0;
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
inline void WaitUntilConcurrencyPeaks () { WaitUntilConcurrencyPeaks(g_NumThreads); }
|
|
|
|
inline bool IsMaster() {
|
|
return Harness::CurrentTid() == g_Master;
|
|
}
|
|
|
|
inline bool IsThrowingThread() {
|
|
return g_ExceptionInMaster ^ IsMaster() ? true : false;
|
|
}
|
|
|
|
class CancellatorTask : public tbb::task {
|
|
static volatile bool s_Ready;
|
|
tbb::task_group_context &m_groupToCancel;
|
|
intptr_t m_cancellationThreshold;
|
|
|
|
tbb::task* execute () {
|
|
Harness::ConcurrencyTracker ct;
|
|
s_Ready = true;
|
|
while ( g_CurExecuted < m_cancellationThreshold )
|
|
__TBB_Yield();
|
|
m_groupToCancel.cancel_group_execution();
|
|
g_ExecutedAtLastCatch = g_CurExecuted;
|
|
return NULL;
|
|
}
|
|
public:
|
|
CancellatorTask ( tbb::task_group_context& ctx, intptr_t threshold )
|
|
: m_groupToCancel(ctx), m_cancellationThreshold(threshold)
|
|
{
|
|
s_Ready = false;
|
|
}
|
|
|
|
static void Reset () { s_Ready = false; }
|
|
|
|
static bool WaitUntilReady () {
|
|
const intptr_t limit = 10000000;
|
|
intptr_t n = 0;
|
|
do {
|
|
__TBB_Yield();
|
|
} while( !s_Ready && ++n < limit );
|
|
// should yield once, then continue if Cancellator is ready.
|
|
ASSERT( s_Ready || n == limit, NULL );
|
|
return s_Ready;
|
|
}
|
|
};
|
|
|
|
volatile bool CancellatorTask::s_Ready = false;
|
|
|
|
template<class LauncherTaskT, class CancellatorTaskT>
|
|
void RunCancellationTest ( intptr_t threshold = 1 )
|
|
{
|
|
tbb::task_group_context ctx;
|
|
tbb::empty_task &r = *new( tbb::task::allocate_root(ctx) ) tbb::empty_task;
|
|
r.set_ref_count(3);
|
|
r.spawn( *new( r.allocate_child() ) CancellatorTaskT(ctx, threshold) );
|
|
__TBB_Yield();
|
|
r.spawn( *new( r.allocate_child() ) LauncherTaskT(ctx) );
|
|
TRY();
|
|
r.wait_for_all();
|
|
CATCH_AND_FAIL();
|
|
r.destroy(r);
|
|
}
|