I'm testing an idea for detailed error handling, and want to enable a thread to have the ability to call a 'getlasterror' function when it needs to work with the error. I'm using a cheap and simple pointer-to-pointers for the structs, but also make use of the pthread_t
id to overwrite a previous entry (if the error info was not needed or has been processed).
From the stackoverflow posts How do you query a pthread to see if it is still running? and How do I determine if a pthread is alive?, it seems using pthread_kill
to send a fake signal is potentially unsafe. Is there really no alternative mechanism to check if a pthread with an id exists or not? Or can I disable the ability for thread ids to be reused at runtime? (I'm aware the latter may be a security issue...)
I'd not previously written any code, but I whipped up roughly what my plan would look like below in leafpad (so ignore any syntax errors, if any!). Point of interest is naturally the dynamic cleanup, there's no problem if the application is closing. Any other alternative ideas would also be welcome :)
If applicable, this will be a client/server program, hence a new thread will exist with each accept()
.
struct error_info_structs
{
struct error_info** errs; // error_info struct with details
pthread_t** tids; // thread ids for each struct
uint32_t num; // number of error_info structs and thread ids
pthread_mutex_lock lock; // runtime locker
};
struct error_info_structs g_errs;
// assume we've done necessary initialization...
struct error_info*
get_last_runtime_error()
{
struct error_info* retval = NULL;
pthread_t tid = pthread_self();
pthread_mutex_lock(&g_errs.lock);
for ( uint32_t i = 0; i < g_errs.num; i++ )
{
if ( pthread_equal(g_errs.tids[i], tid) )
{
retval = g_errs.errs[i];
goto release_lock;
}
}
release_lock:
pthread_mutex_unlock(&g_errs.lock);
return retval;
}
void
raise_runtime_error(struct error_info* ei)
{
pthread_t tid = pthread_self();
pthread_mutex_lock(&g_errs.lock);
for ( uint32_t i = 0; i < g_errs.num; i++ )
{
if ( pthread_equal(g_errs.tids[i], tid) )
{
// replace existing
memcpy(&g_errs.errs[i], ei, sizeof(error_info));
goto release_lock;
}
/*
* Dynamic cleanup to lower risk of resource exhaustion.
* Do it here, where we actually allocate the memory, forcing
* this to be processed at least whenever a new thread raises
* an error.
*/
if ( pthread_kill(g_errs.tids[i], 0) != 0 )
{
// doesn't exist, free memory. safe to adjust counter.
free(g_errs.errs[i]);
free(g_errs.tids[i]);
g_errs.num--;
}
}
/*
* first error reported by this thread id. allocate memory to hold its
* details, eventually free when thread no longer exists.
*/
struct error_info* newei = malloc(sizeof(struct error_info));
if ( newei == NULL )
{
goto release_lock;
}
pthread_t* newt = malloc(sizeof(pthread_t));
if ( newt == NULL )
{
free(newei);
goto release_lock;
}
// realloc-bits omitted
g_errs.errs[g_errs.num] = newei;
g_errs.tids[g_errs.num] = newt;
g_errs.num++;
release_lock:
pthread_mutex_unlock(&g_errs.lock);
}