获取一个协程的完整堆栈跟踪,包括协程的恢复位置

一个简单的例子:

coroutine.resume(coroutine.create(function()
    print(debug.traceback())
end))
print(debug.traceback())

它的输出如下:

stack traceback:
        ./v.lua:2: in function <./v.lua:1>
stack traceback:
        ./v.lua:4: in main chunk
        [C]: in ?

它表明协程内部的traceback不知道它如何被恢复,所以xxx: in main chunk并不会出现。

如何获取协程中的完整堆栈跟踪呢?

点赞
用户14515471
用户14515471

我在这里找到了一个解决办法。

由于一个 Lua VM 在一个时刻只有一个执行点(这也是为什么一个完整的调用栈是必须存在的),我们可以手动记录恢复的信息。

在 lua 中,手动创建一个恢复状态的栈跟踪。

local xresume = coroutine.resume
local xtrace = debug.traceback

-- 这里有魔法!要注意主线程。
local mainthr = coroutine.running()      -- 必须进行捕获。
debug.traceback = function(athr)
  if athr then return xtrace(athr) end  -- 不感兴趣指定的线程。
  return xtrace(mainthr)
end

coroutine.resume = function(thr, ...)
  -- 另一种魔法。
  local uptrace = debug.traceback
  debug.traceback = function(athr)
    if athr then return xtrace(athr) end  -- 不感兴趣指定的线程。
    return xtrace(thr)     -- 跟踪 thr 的堆栈。
      .. '\n' .. uptrace() -- 从 thr 的恢复点开始跟踪。
  end

  local result = { xresume(thr, ...) }
  debug.traceback = uptrace
  return table.unpack(result)
end

其他提示:

  • 使用全局表存储线程也可以起作用。但仍需捕获主线程,以便在任何地方都能跟踪它。

  • 在 C 函数中编写代码可以防止进入挂钩的 coroutine.resumedebug.traceback,从而提供更清晰的输出。

  • 当未调用 debug.traceback 时,您不会遇到太大的性能损失。

2021-03-25 08:22:06