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

This commit is contained in:
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> // Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#include "interface/thread.h" #include "interface/thread.h"
#include "interface/mutex.h" #include "interface/mutex.h"
//#include "interface/semaphore.h"
#include "interface/debug.h" #include "interface/debug.h"
#include "core/log.h" #include "core/log.h"
#include <c55/os.h> #include <c55/os.h>
#include <deque>
#ifdef _WIN32 #ifdef _WIN32
#include "ports/windows_compat.h" #include "ports/windows_compat.h"
#else #else
@ -17,6 +15,13 @@
namespace interface { 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; int g_thread_name_counter = 0;
interface::Mutex g_thread_name_counter_mutex; interface::Mutex g_thread_name_counter_mutex;
@ -29,12 +34,14 @@ struct CThread: public Thread
ThreadedThing *m_thing; ThreadedThing *m_thing;
pthread_t m_thread; pthread_t m_thread;
bool m_thread_exists = false; 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) CThread(ThreadedThing *thing)
{ {
m_thing = thing; m_thing = thing;
//m_thread_exit_sem.post();
} }
~CThread() ~CThread()
@ -46,15 +53,17 @@ struct CThread: public Thread
log_e(MODULE, "Thread %p (%s) destructed but not stopped", log_e(MODULE, "Thread %p (%s) destructed but not stopped",
this, cs(m_name)); this, cs(m_name));
} }
/*request_stop();
join();*/
//m_thread_exit_sem.wait();
delete m_thing; delete m_thing;
} }
static void* run_thread(void *arg) static void* run_thread(void *arg)
{ {
CThread *thread = (CThread*)arg; CThread *thread = (CThread*)arg;
// Set pointer in thread-local storage
g_current_thread_key.set(thread);
// Get name from parameter
ss_ thread_name; ss_ thread_name;
{ {
interface::MutexScope ms(thread->m_mutex); 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)); log_d(MODULE, "Thread started: %p (%s)", thread, cs(thread_name));
// Set name // Set thread name
if(!thread_name.empty()){ if(!thread_name.empty()){
ss_ limited_name = thread_name.size() <= 15 ? ss_ limited_name = thread_name.size() <= 15 ?
thread_name : thread_name.substr(0, 15); thread_name : thread_name.substr(0, 15);
@ -85,19 +94,30 @@ struct CThread: public Thread
} catch(std::exception &e){ } catch(std::exception &e){
log_w(MODULE, "ThreadThing of thread %p (%s) failed: %s", log_w(MODULE, "ThreadThing of thread %p (%s) failed: %s",
arg, cs(thread_name), e.what()); arg, cs(thread_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::StoredBacktrace bt;
interface::debug::get_exception_backtrace(bt); interface::debug::get_exception_backtrace(bt);
interface::debug::log_backtrace(bt, interface::debug::log_backtrace(bt,
"Backtrace in ThreadThing("+thread_name+") for "+ "Backtrace in ThreadThing("+thread_name+") for "+
bt.exception_name+"(\""+e.what()+"\")"); bt.exception_name+"(\""+e.what()+"\")");
} }
}
log_d(MODULE, "Thread %p (%s) exit", arg, cs(thread_name)); log_d(MODULE, "Thread %p (%s) exit", arg, cs(thread_name));
{ {
interface::MutexScope ms(thread->m_mutex); interface::MutexScope ms(thread->m_mutex);
thread->m_running = false; thread->m_running = false;
} }
//thread->m_thread_exit_sem.post();
pthread_exit(NULL); pthread_exit(NULL);
} }
@ -115,7 +135,6 @@ struct CThread: public Thread
void start() void start()
{ {
//m_thread_exit_sem.wait();
if(m_name.empty()){ if(m_name.empty()){
// Generate a name // Generate a name
interface::MutexScope ms(g_thread_name_counter_mutex); interface::MutexScope ms(g_thread_name_counter_mutex);
@ -168,6 +187,29 @@ struct CThread: public Thread
m_thread_exists = false; 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) Thread* createThread(ThreadedThing *thing)

View File

@ -2,6 +2,8 @@
// Copyright 2014 Perttu Ahola <celeron55@gmail.com> // Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#pragma once #pragma once
#include "core/types.h" #include "core/types.h"
#include "interface/debug.h"
#include <list>
namespace interface namespace interface
{ {
@ -14,6 +16,11 @@ namespace interface
virtual void run(interface::Thread *thread) = 0; virtual void run(interface::Thread *thread) = 0;
}; };
struct ThreadBacktrace {
ss_ thread_name;
interface::debug::StoredBacktrace bt;
};
struct Thread struct Thread
{ {
virtual ~Thread(){} virtual ~Thread(){}
@ -23,6 +30,14 @@ namespace interface
virtual void request_stop() = 0; virtual void request_stop() = 0;
virtual bool stop_requested() = 0; virtual bool stop_requested() = 0;
virtual void join() = 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); Thread* createThread(ThreadedThing *thing);

View File

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