Skip to content

4 debug

bruceEeZhao edited this page Apr 22, 2023 · 1 revision

libgo的debug主要包括三个部分:

  1. 调试开关ENABLE_DEBUGGER
  2. 调试信息打印函数
    1. DebugPrint
    2. RS_DBG
  3. 事件监听Listener

调试开关-ENABLE_DEBUGGER

调试开关是一个宏定义,需要在cmake阶段添加该开关的定义

cmake .. -DENABLE_DEBUGGER=ON # 开启调试模式

调试信息打印函数

DebugPrint

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打印协程同步相关的调试信息

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->

从左到右依次是:

  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,并实现全部方法,例如:

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

Clone this wiki locally