I have an intermittent problem with C++ exception handling when I use multiple tasks ("threads" in normal parlance).
The problem is intermittent, but I can reliably reproduce it with the code below. I am using CCS 3.1 with cgtools 5.1.12, and I have enabled exception handling with the --exceptions command line switch and throw exceptions from time to time. Usually, everything is fine. Every now and then I get a message in red in the Stdout pane:
Assertion failed in file "EDGRTS/throw.cpp", line 1312
(or some other line, but they all trace to the same function __throw() in the source file given). I looked through the RTS source for this file in the cgtools/lib directory, and the failed assertion occurs on this line:
check_assertion(__curr_eh_stack_entry == &curr_throw_stack_entry->throw_marker);
The code in that RTS file is rather complex, but you can see that this has something to do with the stack not matching the expected thrown-exception somehow. (FWIW, I notice that there is no substantive change to throw.cpp in cgtools 6.1.5, which I have access to on a different computer.) Can anyone give me any hints as to what this error could mean?
Below is a simple program the demonstrates the problem and, before that, an explanation of what it does. I am running on a TMS320C6713 processor, and I am using CCS 3.1.23, cgtools 5.1.12, DSP/BIOS 4.90, and IDE 5.90.100.9. I used the default DSP/BIOS configuration that is provided when I do "File | New | DSP/BIOS Configuration ..." for my processor, save that I edit it manually to link rts6700_eh.lib instead of rts6700.lib.
An explanation of the program: main() creates 8 tasks of several different priorities. Each task runs the same function, Task(), which sits in an infinite loop and throws and catches exceptions. It makes calls into the standard library functions printf() and rand(), and because those two function maintain internal state (e.g., buffering and the current position in the pseudo-random sequence), I use DSP/BIOS semaphores, wrapped up in some non-throwing C++ helper classes, to synchronize access to them.
Each task sometimes throws and catches an exception based on a value of rand(), and then each task sleeps for a random amount of time. All this pseudo-randomness is intended to recreate the indeterminacy in execution order of my production code, which is exhibiting this same problem. The higher priority tasks usually sleep longer in order not to starve the lower priority tasks.
If you run the program, you'll see it humming along, throwing and catching exceptions like mad in various threads. After a few minutes of this the RTS assertion in EDGRTS/throw.cpp that I mentioned below shows up for no obvious reason and the program aborts. I have checked for stack overflows on the kernel and the tasks in the kernel object viewer, but none of them are even close to their respective limits. One other hypothesis is that the exception handling is not thread-safe.
Can anyone give me any insight into this problem? Is there any work-around other than removing exception handling (not an easy option for our production code!)? Is the exception handling thread-safe?
TIA! --M
Here's the code:
#include <std.h>
#include <tsk.h>
#include <exception> // for exception
#include <cstdio> // for printf()
#include <cstdlib> // for rand()
#include <new> // for bad_alloc
using namespace std;
// All synchronization operations are no-throw
class Semaphore
{
public:
Semaphore( const Int cnt ) : m_sem( SEM_create( cnt, NULL ) ) {}
~Semaphore() { SEM_delete( m_sem ); }
void Reset( Int count=0 ) { SEM_reset( m_sem, count ); }
void Post() { SEM_post( m_sem ); }
void iPost() { SEM_ipost( m_sem ); }
bool Pend( const Uns timeout = SYS_FOREVER ) { return SEM_pend( m_sem, timeout ); }
Int GetCount() const { return SEM_count( m_sem ); }
// Helper class to automatically lock and unlock the semaphore
class ScopedLock
{
public:
ScopedLock( Semaphore& sem )
: m_sem( &sem )
{
(void) m_sem->Pend();
}
~ScopedLock()
{
m_sem->Post();
}
private:
Semaphore* const m_sem;
ScopedLock( const ScopedLock& );
ScopedLock& operator=( const ScopedLock& );
};
private:
// Data members
SEM_Handle m_sem;
// Disable copying
Semaphore( const Semaphore& );
Semaphore& operator=( const Semaphore& );
};
class Mutex
{
public:
Mutex() : m_sem( 1 ) {}
void Lock() { (void) m_sem.Pend(); }
void Unlock() { m_sem.Post(); }
// Helper class to automatically lock and unlock the semaphore
class ScopedLock
{
public:
ScopedLock( Mutex& mutex ) : m_mutex( &mutex ) { m_mutex->Lock(); }
~ScopedLock() { m_mutex->Unlock(); }
private:
Mutex* const m_mutex;
ScopedLock( const ScopedLock& );
ScopedLock& operator=( const ScopedLock& );
};
private:
// Data members
Semaphore m_sem;
// Disable copying
Mutex( const Mutex& );
Mutex& operator=( const Mutex& );
};
static Mutex s_randMutex;
static Mutex s_ioMutex;
int Rand()
{
Mutex::ScopedLock lock( s_randMutex );
return rand() * rand();
}
void Printf( const char* const msg, int id, const char* const emsg = 0 )
{
// Protect i/o
Mutex::ScopedLock lock( s_ioMutex );
if( emsg )
{
printf( msg, id, emsg );
}
else
{
printf( msg, id );
}
}
struct MyException : exception
{
const char* const m_msg;
MyException( const char* const msg ) : m_msg( msg ) {}
virtual const char* what() const throw() { return m_msg; }
};
extern "C"
Void Task()
{
const TSK_Handle self = TSK_self();
const int priority = TSK_getpri( self );
const int id = int(self) | (priority << 20);
Printf( "Task %x starting\n", id );
for( ;; )
{
try
{
if( 0 == Rand() % 10 )
{
Printf( "Throwing in (%x)\n", id );
throw MyException( "D'oh!" );
}
}
catch( const exception& e )
{
Printf( "Exception (%x): %s\n", id, e.what() );
}
catch( const bad_alloc& )
{
Printf( "Exception (%x): bad_alloc\n", id );
}
catch( ... )
{
Printf( "Exception (%x): unknown\n", id );
}
TSK_sleep( Rand() % (2*priority) );
}
}
const int N_TASKS = 8;
TSK_Handle s_tasks[ N_TASKS ];
int main()
{
for( int n=0; n < N_TASKS; ++n )
{
if( s_tasks[ n ] = TSK_create( (Fxn)&Task, 0, n ) )
{
TSK_setpri( s_tasks[ n ], 1 + n / 2 );
}
else
{
printf( "Unable to create task %d\n", n );
}
}
printf( "Main finished: %d tasks created.\n", N_TASKS );
}