forked from yyzybb537/libgo
-
Notifications
You must be signed in to change notification settings - Fork 2
4 debug
bruceEeZhao edited this page Apr 22, 2023
·
1 revision
libgo的debug主要包括三个部分:
- 调试开关
ENABLE_DEBUGGER
- 调试信息打印函数
DebugPrint
RS_DBG
- 事件监听
Listener
调试开关是一个宏定义,需要在cmake阶段添加该开关的定义
cmake .. -DENABLE_DEBUGGER=ON # 开启调试模式
DebugPrint 是一个宏定义的函数,通过第三行的 debug & (type)
打印特定类型的调试信息。
#define DebugPrint(type, fmt, ...) \
do { \
if (UNLIKELY(::co::CoroutineOptions::getInstance().debug & (type))) { \
::co::ErrnoStore es; \
std::unique_lock<std::mutex> lock(::co::gDbgLock); \
fprintf(::co::CoroutineOptions::getInstance().debug_output, "[%s][%05d][%04d][%06d]%s:%d:(%s)\t " fmt "\n", \
::co::GetCurrentTimeStr().c_str(),\
::co::GetCurrentProcessID(), ::co::GetCurrentThreadID(), ::co::GetCurrentCoroID(), \
::co::BaseFile(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__); \
fflush(::co::CoroutineOptions::getInstance().debug_output); \
} \
} while(0)
其中type表示调试信息的类别:
static const uint64_t dbg_none = 0;
static const uint64_t dbg_all = ~(uint64_t)0;
static const uint64_t dbg_hook = 0x1;
static const uint64_t dbg_yield = 0x1 << 1;
static const uint64_t dbg_scheduler = 0x1 << 2;
static const uint64_t dbg_task = 0x1 << 3;
static const uint64_t dbg_switch = 0x1 << 4;
static const uint64_t dbg_ioblock = 0x1 << 5;
static const uint64_t dbg_suspend = 0x1 << 6;
static const uint64_t dbg_exception = 0x1 << 7;
static const uint64_t dbg_syncblock = 0x1 << 8;
static const uint64_t dbg_timer = 0x1 << 9;
static const uint64_t dbg_scheduler_sleep = 0x1 << 10;
static const uint64_t dbg_sleepblock = 0x1 << 11;
static const uint64_t dbg_spinlock = 0x1 << 12;
static const uint64_t dbg_fd_ctx = 0x1 << 13;
static const uint64_t dbg_debugger = 0x1 << 14;
static const uint64_t dbg_signal = 0x1 << 15;
static const uint64_t dbg_channel = 0x1 << 16;
static const uint64_t dbg_thread = 0x1 << 17;
static const uint64_t dbg_rutex = 0x1 << 18;
static const uint64_t dbg_mutex = 0x1 << 19;
static const uint64_t dbg_cond_v = 0x1 << 20;
static const uint64_t dbg_test = 0x1 << 21;
static const uint64_t dbg_sys_max = dbg_cond_v;
DebugPrint
函数使用方式如下:
DebugPrint(dbg_exception, "throw exception %d:%s",
(int)code, GetCoErrorCategory().message((int)code).c_str());
通过设置::co::CoroutineOptions::getInstance().debug
的值可以打印对应类别的调试信息,
需要打印的调试信息的类型,需要在程序中对进行设置,如下:
co_opt.debug = co::dbg_scheduler | co::dbg_task;
// debug 类型定义在 commom/config.h 中
RS_DBG
打印协程同步相关的调试信息
RS_DBG
实际使用的仍是DebugPrint
,但需要打开OPEN_ROUTINE_SYNC_DEBUG
开关
可以直接在libg0/common/cmake_config.h
文件中添加#define OPEN_ROUTINE_SYNC_DEBUG 1
libgo提供了协程事件监听机制,以在协程的特定阶段完成一些事情,例如在协程进入之前打印一些调试信息。
libgo提供了一个Listener类,类中的虚函数需要程序员自己定义:
class Listener
{
public:
/**
* 协程事件监听器
* 注意:其中所有的回调方法都不允许抛出异常
*/
class TaskListener {
public:
/**
* 协程被创建时被调用
* (注意此时并未运行在协程中)
*
* @prarm task_id 协程ID
* @prarm eptr
*/
virtual void onCreated(uint64_t task_id) noexcept {
}
/**
* 每次协程切入前调用
* (注意此时并未运行在协程中)
*
* @prarm task_id 协程ID
* @prarm eptr
*/
virtual void onSwapIn(uint64_t task_id) noexcept {
}
/**
* 协程开始运行
* (本方法运行在协程中)
*
* @prarm task_id 协程ID
* @prarm eptr
*/
virtual void onStart(uint64_t task_id) noexcept {
}
/**
* 每次协程切出前调用
* (本方法运行在协程中)
*
* @prarm task_id 协程ID
* @prarm eptr
*/
virtual void onSwapOut(uint64_t task_id) noexcept {
}
/**
* 协程正常运行结束(无异常抛出)
* (本方法运行在协程中)
*
* @prarm task_id 协程ID
*/
virtual void onCompleted(uint64_t task_id) noexcept {
}
/**
* 协程抛出未被捕获的异常(本方法运行在协程中)
* @prarm task_id 协程ID
* @prarm eptr 抛出的异常对象指针,可对本指针赋值以修改异常对象,
* 异常将使用 CoroutineOptions.exception_handle 中
* 配置的方式处理;赋值为nullptr则表示忽略此异常
* !!注意:当 exception_handle 配置为 immedaitely_throw 时本事件
* !!与 onFinished() 均失效,异常发生时将直接抛出并中断程序的运行,同时生成coredump
*/
virtual void onException(uint64_t task_id, std::exception_ptr& eptr) noexcept {
}
/**
* 协程运行完成,if(eptr) 为false说明协程正常结束,为true说明协程抛出了了异常
*(本方法运行在协程中)
*
* @prarm task_id 协程ID
*/
virtual void onFinished(uint64_t task_id) noexcept {
}
virtual ~TaskListener() noexcept = default;
// s: Scheduler,表示该方法运行在调度器上下文中
// c: Coroutine,表示该方法运行在协程上下文中
//
// -->[c]onCompleted->
// | |
// [s]onCreated-->[s]onSwapIn-->[c]onStart->*--->[c]onSwapOut-- -->[c]onFinished-->[c]onSwapOut
// |\ | |
// | \<-[s]onSwapIn--V |
// | |
// -->[c]onException->
};
public:
ALWAYS_INLINE static TaskListener*& GetTaskListener() {
static TaskListener* task_listener = nullptr;
return task_listener;
}
static void SetTaskListener(TaskListener* listener) {
GetTaskListener() = listener;
}
};
程序是如何通过上面的类实现对协程事件的监听呢?
下面的代码截取自Processer::Process()
#if ENABLE_DEBUGGER
DebugPrint(dbg_switch, "enter task(%s)", runningTask_->DebugInfo());
if (Listener::GetTaskListener())
Listener::GetTaskListener()->onSwapIn(runningTask_->id_);
#endif
实际上就是在特定的位置,判断有没有设置listener,此处的代码就是在执行runningTask_->SwapIn();
之前执行的,此时是调度线程中,正如代码中的注释,执行不同的函数时所处的线程或协程状态为:
// s: Scheduler,表示该方法运行在调度器上下文中
// c: Coroutine,表示该方法运行在协程上下文中
//
// -->[c]onCompleted->
// | |
// [s]onCreated-->[s]onSwapIn-->[c]onStart->*--->[c]onSwapOut-- -->[c]onFinished-->[c]onSwapOut
// |\ | |
// | \<-[s]onSwapIn--V |
// | |
// -->[c]onException->
从左到右依次是:
-
Scheduler::CreateTask
创建好一个协程,在添加到new
队列之前 -
Processer::Process()
从runnable队列中取出一个协程,执行SwapIn
之前 -
Task::Run()
协程执行协程函数之前 - 协程执行,期间可能遇到的各种情况(yield,suspend,函数运行结束)等
-
Task::Run()
协程执行协程函数完成 -
Processer::CoYield()
让出CPU之前
用户需要定义一个类,继承co::Listener::TaskListener
,并实现全部方法,例如:
class CoListenerSample: public co::Listener::TaskListener {
public:
virtual void onCreated(uint64_t task_id) noexcept {
cout << "onCreated task_id=" << task_id << endl;
}
virtual void onSwapIn(uint64_t task_id) noexcept {
cout << "onSwapIn task_id=" << task_id << endl;
}
......
}
main函数中需要设置使用定义的监听器:
int main(int argc, char** argv) {
CoListenerSample listener;
//设置协程监听器,如果设置为NULL则为取消监听
co::Listener::SetTaskListener(&listener);
//将异常的处理方式置为使用listener处理
co_opt.exception_handle = co::eCoExHandle::on_listener;
......
co_sched.Start();
return 0;
}
详见example12_listener.cpp