interface/thread, server/state: Support callback-chain backtraces in all threads

master
Perttu Ahola 2014-10-30 11:54:48 +02:00
parent 873213aae9
commit 0115364cbe
3 changed files with 113 additions and 55 deletions

View File

@ -2,11 +2,9 @@
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#include "interface/thread.h"
#include "interface/mutex.h"
//#include "interface/semaphore.h"
#include "interface/debug.h"
#include "core/log.h"
#include <c55/os.h>
#include <deque>
#ifdef _WIN32
#include "ports/windows_compat.h"
#else
@ -17,6 +15,13 @@
namespace interface {
ThreadLocalKey g_current_thread_key;
Thread* Thread::get_current_thread()
{
return (Thread*)g_current_thread_key.get();
}
int g_thread_name_counter = 0;
interface::Mutex g_thread_name_counter_mutex;
@ -29,12 +34,14 @@ struct CThread: public Thread
ThreadedThing *m_thing;
pthread_t m_thread;
bool m_thread_exists = false;
//interface::Semaphore m_thread_exit_sem;
// Debug data
std::list<ThreadBacktrace> m_backtraces;
Thread *m_caller_thread = nullptr;
CThread(ThreadedThing *thing)
{
m_thing = thing;
//m_thread_exit_sem.post();
}
~CThread()
@ -46,15 +53,17 @@ struct CThread: public Thread
log_e(MODULE, "Thread %p (%s) destructed but not stopped",
this, cs(m_name));
}
/*request_stop();
join();*/
//m_thread_exit_sem.wait();
delete m_thing;
}
static void* run_thread(void *arg)
{
CThread *thread = (CThread*)arg;
// Set pointer in thread-local storage
g_current_thread_key.set(thread);
// Get name from parameter
ss_ thread_name;
{
interface::MutexScope ms(thread->m_mutex);
@ -62,7 +71,7 @@ struct CThread: public Thread
}
log_d(MODULE, "Thread started: %p (%s)", thread, cs(thread_name));
// Set name
// Set thread name
if(!thread_name.empty()){
ss_ limited_name = thread_name.size() <= 15 ?
thread_name : thread_name.substr(0, 15);
@ -85,11 +94,23 @@ struct CThread: public Thread
} catch(std::exception &e){
log_w(MODULE, "ThreadThing of thread %p (%s) failed: %s",
arg, cs(thread_name), e.what());
interface::debug::StoredBacktrace bt;
interface::debug::get_exception_backtrace(bt);
interface::debug::log_backtrace(bt,
"Backtrace in ThreadThing("+thread_name+") for "+
bt.exception_name+"(\""+e.what()+"\")");
if(!thread->m_backtraces.empty()){
ss_ ex_name;
for(const interface::ThreadBacktrace &bt_step :
thread->m_backtraces){
if(!bt_step.bt.exception_name.empty())
ex_name = bt_step.bt.exception_name;
interface::debug::log_backtrace(bt_step.bt,
"Backtrace in M["+bt_step.thread_name+"] for "+
ex_name+"(\""+e.what()+"\")");
}
} else {
interface::debug::StoredBacktrace bt;
interface::debug::get_exception_backtrace(bt);
interface::debug::log_backtrace(bt,
"Backtrace in ThreadThing("+thread_name+") for "+
bt.exception_name+"(\""+e.what()+"\")");
}
}
log_d(MODULE, "Thread %p (%s) exit", arg, cs(thread_name));
@ -97,7 +118,6 @@ struct CThread: public Thread
interface::MutexScope ms(thread->m_mutex);
thread->m_running = false;
}
//thread->m_thread_exit_sem.post();
pthread_exit(NULL);
}
@ -115,7 +135,6 @@ struct CThread: public Thread
void start()
{
//m_thread_exit_sem.wait();
if(m_name.empty()){
// Generate a name
interface::MutexScope ms(g_thread_name_counter_mutex);
@ -168,6 +187,29 @@ struct CThread: public Thread
m_thread_exists = false;
}
}
// Debugging interface (not thread-safe; access only from thread itself)
ss_ get_name()
{
interface::MutexScope ms(m_mutex);
return m_name;
}
std::list<ThreadBacktrace>& ref_backtraces()
{
return m_backtraces;
}
void set_caller_thread(Thread *thread)
{
m_caller_thread = thread;
}
Thread* get_caller_thread()
{
return m_caller_thread;
}
};
Thread* createThread(ThreadedThing *thing)

View File

@ -2,6 +2,8 @@
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#pragma once
#include "core/types.h"
#include "interface/debug.h"
#include <list>
namespace interface
{
@ -14,6 +16,11 @@ namespace interface
virtual void run(interface::Thread *thread) = 0;
};
struct ThreadBacktrace {
ss_ thread_name;
interface::debug::StoredBacktrace bt;
};
struct Thread
{
virtual ~Thread(){}
@ -23,6 +30,14 @@ namespace interface
virtual void request_stop() = 0;
virtual bool stop_requested() = 0;
virtual void join() = 0;
// Debugging interface (not thread-safe; access only from thread itself)
virtual ss_ get_name() = 0;
virtual std::list<ThreadBacktrace>& ref_backtraces() = 0;
virtual void set_caller_thread(Thread *thread) = 0;
virtual Thread* get_caller_thread() = 0;
static Thread* get_current_thread();
};
Thread* createThread(ThreadedThing *thing);

View File

@ -67,7 +67,6 @@ struct ModuleContainer
// Allows directly executing code in the module thread
const std::function<void(interface::Module*)> *direct_cb = nullptr;
std::exception_ptr direct_cb_exception = nullptr;
ModuleContainer *direct_cb_caller_mc = nullptr;
// The actual event queue
std::deque<Event> event_queue; // Push back, pop front
// Protects direct_cb and event_queue
@ -79,14 +78,10 @@ struct ModuleContainer
// post() when direct_cb becomes free, wait() for that to happen
interface::Semaphore direct_cb_free_sem;
// Holds the backtraces along the way of a direct callback chain initiated
// by this module. Cleared when beginning to execute a direct callback. Read
// when event() (and maybe something else) returns an uncatched exception.
struct BacktraceStep {
ss_ module_name; // From which thread this backtrace is
interface::debug::StoredBacktrace bt;
};
std::list<ModuleContainer::BacktraceStep> direct_cb_exception_backtraces;
// NOTE: thread-ref_backtraces() Holds the backtraces along the way of a
// direct callback chain initiated by this module. Cleared when beginning to
// execute a direct callback. Read when event() (and maybe something else)
// returns an uncatched exception.
ModuleContainer(interface::Server *server = nullptr,
interface::ThreadLocalKey *thread_local_key = NULL,
@ -200,8 +195,8 @@ struct ModuleContainer
cs(info.name));
direct_cb = &cb;
direct_cb_exception = nullptr;
direct_cb_caller_mc = caller_mc;
direct_cb_exception_backtraces.clear();
thread->set_caller_thread(interface::Thread::get_current_thread());
thread->ref_backtraces().clear();
event_queue_sem.post();
}
log_t(MODULE, "execute_direct_cb[%s]: Waiting for execution to finish",
@ -215,7 +210,7 @@ struct ModuleContainer
// Grab execution result
std::exception_ptr eptr = direct_cb_exception;
direct_cb_exception = nullptr; // Not to be used anymore
direct_cb_caller_mc = nullptr; // Not used anymore
thread->set_caller_thread(nullptr);
// Set direct_cb to be free again
direct_cb_free_sem.post();
// Handle execution result
@ -323,21 +318,24 @@ void ModuleThread::handle_direct_cb(
// then determines the final result of the exception.
eptr = std::current_exception();
// If called from a module
if(mc->direct_cb_caller_mc){
// Find out the original MC that initiated this direct_cb chain
ModuleContainer *orig_mc = mc->direct_cb_caller_mc;
while(orig_mc->direct_cb_caller_mc){
orig_mc = orig_mc->direct_cb_caller_mc;
// If called from another thread
interface::Thread *current_thread =
interface::Thread::get_current_thread();
if(current_thread->get_caller_thread()){
// Find out the original thread that initiated this direct_cb chain
interface::Thread *orig_thread =
current_thread->get_caller_thread();
while(orig_thread->get_caller_thread()){
orig_thread = orig_thread->get_caller_thread();
}
// Insert exception backtrace to original chain initiator's
// backtrace list, IF the list is empty
if(orig_mc->direct_cb_exception_backtraces.empty()){
ModuleContainer::BacktraceStep bt_step;
bt_step.module_name = mc->info.name; // Current module name
if(orig_thread->ref_backtraces().empty()){
interface::ThreadBacktrace bt_step;
bt_step.thread_name = current_thread->get_name();
interface::debug::get_exception_backtrace(bt_step.bt);
orig_mc->direct_cb_exception_backtraces.push_back(bt_step);
orig_thread->ref_backtraces().push_back(bt_step);
}
}
}
@ -369,14 +367,14 @@ void ModuleThread::handle_event(Event &event)
"failed: "+e.what());
log_w(MODULE, "M[%s]->event() failed: %s",
cs(mc->info.name), e.what());
if(!mc->direct_cb_exception_backtraces.empty()){
if(!mc->thread->ref_backtraces().empty()){
ss_ ex_name;
for(const ModuleContainer::BacktraceStep &bt_step :
mc->direct_cb_exception_backtraces){
for(const interface::ThreadBacktrace &bt_step :
mc->thread->ref_backtraces()){
if(!bt_step.bt.exception_name.empty())
ex_name = bt_step.bt.exception_name;
interface::debug::log_backtrace(bt_step.bt,
"Backtrace in M["+bt_step.module_name+"] for "+
"Backtrace in M["+bt_step.thread_name+"] for "+
ex_name+"(\""+e.what()+"\")");
}
} else {
@ -1039,30 +1037,33 @@ struct CState: public State, public interface::Server
bool ok = mc->execute_direct_cb(cb, eptr, caller_mc);
(void)ok; // Unused
if(eptr){
// If not being called by a module thread, there's nowhere we can
// store the backtrace (and it wouldn't make sense anyway as there
// is no callback chain)
if(caller_mc == nullptr){
interface::Thread *current_thread =
interface::Thread::get_current_thread();
// If not being called by a thread, there's nowhere we can store the
// backtrace (and it wouldn't make sense anyway as there is no
// callback chain)
if(current_thread == nullptr){
std::rethrow_exception(eptr);
}
// NOTE: In each ModuleContainer there is a pointer to the
// ModuleContainer that is currently doing a direct call, or
// nullptr if a direct call is not being executed.
// NOTE: In each Thread there is a pointer to the Thread that is
// currently doing a direct call, or nullptr if a direct call
// is not being executed.
// NOTE: The parent callers in the chain cannot be deleted while
// this function is executing so we can freely access them.
// Get the original MC that initiated this direct_cb chain
ModuleContainer *orig_mc = caller_mc;
while(orig_mc->direct_cb_caller_mc){
orig_mc = orig_mc->direct_cb_caller_mc;
// Find out the original thread that initiated this direct_cb chain
interface::Thread *orig_thread = current_thread;
while(orig_thread->get_caller_thread()){
orig_thread = orig_thread->get_caller_thread();
}
// Insert backtrace to original chain initiator's backtrace list
ModuleContainer::BacktraceStep bt_step;
bt_step.module_name = caller_mc->info.name; // Caller module name
interface::ThreadBacktrace bt_step;
bt_step.thread_name = current_thread->get_name();
interface::debug::get_current_backtrace(bt_step.bt);
orig_mc->direct_cb_exception_backtraces.push_back(bt_step);
orig_thread->ref_backtraces().push_back(bt_step);
// NOTE: When an exception comes uncatched from module->event(), the
// direct_cb backtrace stack can be logged after the backtrace