libgo的debug主要包括三个部分: 1. 调试开关`ENABLE_DEBUGGER` 2. 调试信息打印函数 1. `DebugPrint` 2. `RS_DBG` 3. 事件监听`Listener` # 调试开关-ENABLE_DEBUGGER 调试开关是一个宏定义,需要在cmake阶段添加该开关的定义 ```c cmake .. -DENABLE_DEBUGGER=ON # 开启调试模式 ``` # 调试信息打印函数 ## DebugPrint DebugPrint 是一个宏定义的函数,通过第三行的 `debug & (type)`打印特定类型的调试信息。 ```c #define DebugPrint(type, fmt, ...) \ do { \ if (UNLIKELY(::co::CoroutineOptions::getInstance().debug & (type))) { \ ::co::ErrnoStore es; \ std::unique_lock 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表示调试信息的类别: ```c 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`函数使用方式如下: ```c++ DebugPrint(dbg_exception, "throw exception %d:%s", (int)code, GetCoErrorCategory().message((int)code).c_str()); ``` 通过设置`::co::CoroutineOptions::getInstance().debug`的值可以打印对应类别的调试信息, 需要打印的调试信息的类型,需要在程序中对进行设置,如下: ```c co_opt.debug = co::dbg_scheduler | co::dbg_task; // debug 类型定义在 commom/config.h 中 ``` ## RS_DBG `RS_DBG`打印协程同步相关的调试信息 `RS_DBG`实际使用的仍是`DebugPrint`,但需要打开`OPEN_ROUTINE_SYNC_DEBUG `开关 可以直接在`libg0/common/cmake_config.h`文件中添加`#define OPEN_ROUTINE_SYNC_DEBUG 1` # 事件监听 libgo提供了协程事件监听机制,以在协程的特定阶段完成一些事情,例如在协程进入之前打印一些调试信息。 libgo提供了一个Listener类,类中的虚函数需要程序员自己定义: ```c++ 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()` ```c++ #if ENABLE_DEBUGGER DebugPrint(dbg_switch, "enter task(%s)", runningTask_->DebugInfo()); if (Listener::GetTaskListener()) Listener::GetTaskListener()->onSwapIn(runningTask_->id_); #endif ``` 实际上就是在特定的位置,判断有没有设置listener,此处的代码就是在执行`runningTask_->SwapIn();`之前执行的,此时是调度线程中,正如代码中的注释,执行不同的函数时所处的线程或协程状态为: ```c++ // s: Scheduler,表示该方法运行在调度器上下文中 // c: Coroutine,表示该方法运行在协程上下文中 // // -->[c]onCompleted-> // | | // [s]onCreated-->[s]onSwapIn-->[c]onStart->*--->[c]onSwapOut-- -->[c]onFinished-->[c]onSwapOut // |\ | | // | \<-[s]onSwapIn--V | // | | // -->[c]onException-> ``` 从左到右依次是: 1. `Scheduler::CreateTask`创建好一个协程,在添加到`new`队列之前 2. `Processer::Process()`从runnable队列中取出一个协程,执行`SwapIn`之前 3. `Task::Run()`协程执行协程函数之前 4. 协程执行,期间可能遇到的各种情况(yield,suspend,函数运行结束)等 5. `Task::Run()`协程执行协程函数完成 6. `Processer::CoYield()`让出CPU之前 ## 如何使用 用户需要定义一个类,继承`co::Listener::TaskListener`,并实现全部方法,例如: ```c++ 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函数中需要设置使用定义的监听器: ```c 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`