interface/thread, server/state: Support callback-chain backtraces in all threads
This commit is contained in:
parent
873213aae9
commit
0115364cbe
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user