From 0115364cbe13d007b8feb69670dabeda093f4719 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Thu, 30 Oct 2014 11:54:48 +0200 Subject: [PATCH] interface/thread, server/state: Support callback-chain backtraces in all threads --- src/impl/thread.cpp | 72 +++++++++++++++++++++++++++++-------- src/interface/thread.h | 15 ++++++++ src/server/state.cpp | 81 +++++++++++++++++++++--------------------- 3 files changed, 113 insertions(+), 55 deletions(-) diff --git a/src/impl/thread.cpp b/src/impl/thread.cpp index 68f788c..6e90593 100644 --- a/src/impl/thread.cpp +++ b/src/impl/thread.cpp @@ -2,11 +2,9 @@ // Copyright 2014 Perttu Ahola #include "interface/thread.h" #include "interface/mutex.h" -//#include "interface/semaphore.h" #include "interface/debug.h" #include "core/log.h" #include -#include #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 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& 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) diff --git a/src/interface/thread.h b/src/interface/thread.h index 9c33d58..698c048 100644 --- a/src/interface/thread.h +++ b/src/interface/thread.h @@ -2,6 +2,8 @@ // Copyright 2014 Perttu Ahola #pragma once #include "core/types.h" +#include "interface/debug.h" +#include 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& 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); diff --git a/src/server/state.cpp b/src/server/state.cpp index 841d9ed..239cbe9 100644 --- a/src/server/state.cpp +++ b/src/server/state.cpp @@ -67,7 +67,6 @@ struct ModuleContainer // Allows directly executing code in the module thread const std::function *direct_cb = nullptr; std::exception_ptr direct_cb_exception = nullptr; - ModuleContainer *direct_cb_caller_mc = nullptr; // The actual event queue std::deque 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 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