diff --git a/docs/luadebug/hookmgr.lua b/docs/luadebug/hookmgr.lua index a64b4e9c..6b0c7182 100644 --- a/docs/luadebug/hookmgr.lua +++ b/docs/luadebug/hookmgr.lua @@ -130,9 +130,24 @@ end --- ---@param co thread ---@return thread ----获取coroutine调用方 +---获取coroutine调用方。手动指定的父协程优先,其次才是resume关系。 --- function hookmgr.coroutine_from(co) end +--- +---@param co lightuserdata +---@param parent lightuserdata|nil +---手动指定co的父协程,传nil表示清除。co是协程标识(见rdebug.threadptr)。 +--- +function hookmgr.coroutine_setparent(co, parent) +end + +--- +---@param co lightuserdata +---通知调试器co已结束,清掉它的映射以及所有指向它的映射。 +--- +function hookmgr.coroutine_dead(co) +end + return hookmgr diff --git a/extension/script/backend/worker.lua b/extension/script/backend/worker.lua index 83e1e468..09e3a633 100644 --- a/extension/script/backend/worker.lua +++ b/extension/script/backend/worker.lua @@ -234,7 +234,8 @@ end local function coroutineFrom(L) if hookmgr.coroutine_from then - return coroutineTree[L] or hookmgr.coroutine_from(L) + -- 手动指定的父协程优先,其次是native推导的,最后才是Lua兜底表 + return hookmgr.coroutine_from(L) or coroutineTree[L] end return coroutineTree[L] end @@ -270,11 +271,17 @@ function CMD.stackTrace(pkg) start = start + skipFrame local L = baseL local coroutineId = 0 + local visited = {} local finish repeat hookmgr.sethost(L) local curL = L L = coroutineFrom(curL) + -- 手动指定的父协程可能构成环 + if visited[curL] then + break + end + visited[curL] = true if stackFrame[curL] == nil then local n; finish, n = stackTrace(res, coroutineId, start, levels) @@ -947,16 +954,19 @@ function event.thread(co, type) if not debuggeeReady() then return end -- L是触发事件的协程,即co的调用方(父协程) local L = hookmgr.gethost() - if co then + if co and rdebug.threadptr then + -- 退出事件也可能只是yield,要确认协程真的结束了 + local dead = type == 1 and rdebug.costatus(co) == "dead" -- co是调试目标里的协程,转成地址才能在调试器侧标识它 - co = rdebug.threadptr and rdebug.threadptr(co) - if co then - if type == 0 then - coroutineTree[co] = L - hookmgr.updatehookmask(co) - return - elseif type == 1 then - coroutineTree[co] = nil + co = rdebug.threadptr(co) + if type == 0 then + coroutineTree[co] = L + hookmgr.updatehookmask(co) + return + elseif type == 1 then + coroutineTree[co] = nil + if dead and hookmgr.coroutine_dead then + hookmgr.coroutine_dead(co) end end end @@ -972,6 +982,16 @@ function event.setThreadName(name) sendToMaster 'setThreadName' (name) end +function event.setCoroutineParent(co, parent) + if not debuggeeReady() then return end + if not (rdebug.threadptr and hookmgr.coroutine_setparent) then return end + -- co和parent是调试目标里的协程,转成地址才能在调试器侧标识它们 + co = rdebug.threadptr(co) + if co then + hookmgr.coroutine_setparent(co, parent and rdebug.threadptr(parent)) + end +end + function event.exit() sendToMaster 'exitWorker' {} channel.destroy(WorkerChannel) diff --git a/extension/script/debugger.lua b/extension/script/debugger.lua index 4299cd62..061e6af8 100644 --- a/extension/script/debugger.lua +++ b/extension/script/debugger.lua @@ -39,6 +39,11 @@ if debug.getregistry()["lua-debug"] then return self end + function empty:set_coroutine_parent(co, parent) + dbg:set_coroutine_parent(co, parent) + return self + end + function empty:setup_patch() return self end @@ -219,6 +224,12 @@ function dbg:set_wait(name, f) return self end +-- 指定协程co的父协程,堆栈窗口里会把parent的堆栈拼在co后面。parent为nil表示清除。 +function dbg:set_coroutine_parent(co, parent) + self:event("setCoroutineParent", co, parent) + return self +end + function dbg:setup_patch() local ERREVENT_ERRRUN = 0x02 local rawxpcall = xpcall @@ -260,6 +271,16 @@ function dbg:setup_patch() end end + -- 被close的协程也是结束了,不然它的父链接会一直留着 + local rawcoroutineclose = coroutine.close + if rawcoroutineclose then + function coroutine.close(co) + local ok, err = rawcoroutineclose(co) + self:event("thread", co, 1) + return ok, err + end + end + return self end diff --git a/src/luadebug/rdebug_hookmgr.cpp b/src/luadebug/rdebug_hookmgr.cpp index eb155fe9..adb68a2d 100644 --- a/src/luadebug/rdebug_hookmgr.cpp +++ b/src/luadebug/rdebug_hookmgr.cpp @@ -342,7 +342,54 @@ struct hookmgr { // thread // int thread_mask = 0; + // 自动推导的调用方:child -> resumer,只有补丁Lua才会有内容 bee::flatmap coroutine_tree; + // 手动指定的父协程:child -> parent + bee::flatmap coroutine_parent; + + // 协程结束后清掉它的映射,以及所有指向它的映射,否则会留下悬垂的lua_State* + void coroutine_dead(lua_State* co) { + coroutine_tree.erase(co); + coroutine_parent.erase(co); + if (coroutine_parent.empty()) { + return; + } + bee::dynarray children(coroutine_parent.size()); + size_t n = 0; + for (auto [child, parent] : coroutine_parent) { + if (parent == co) { + children[n++] = child; + } + } + for (size_t i = 0; i < n; ++i) { + coroutine_parent.erase(children[i]); + } + } + + void coroutine_setparent(lua_State* co, lua_State* parent) { + if (parent) { + coroutine_parent.insert_or_assign(co, parent); + } else { + coroutine_parent.erase(co); + } + } + + lua_State* coroutine_from(lua_State* co) { + auto p = coroutine_parent.find(co); + if (p) { + return *p; + } +#if defined(LUA_HOOKTHREAD) + auto r = coroutine_tree.find(co); + if (!r) { + return nullptr; + } + return *r; +#else + return nullptr; +#endif + } + #if defined(LUA_HOOKTHREAD) void thread_hookmask(lua_State* hL, int mask) { if (thread_mask != mask) { @@ -360,18 +407,16 @@ struct hookmgr { if (type == 0) { coroutine_tree.insert_or_assign(co, from); } else if (type == 1) { - coroutine_tree.erase(from); + if (lua_status(from) == LUA_YIELD) { + // 只是让出,自动链接下次resume会重来,手动指定的父协程要保留 + coroutine_tree.erase(from); + } else { + coroutine_dead(from); + } } } updatehookmask(co); } - lua_State* coroutine_from(lua_State* co) { - auto r = coroutine_tree.find(co); - if (!r) { - return nullptr; - } - return *r; - } #endif // @@ -760,6 +805,7 @@ static int thread_open(luadbg_State* L) { hookmgr::get_self(L)->thread_open(luadebug::debughost::get(L), luadbg_toboolean(L, 1)); return 0; } +#endif static int coroutine_from(luadbg_State* L) { luadbgL_checktype(L, 1, LUA_TLIGHTUSERDATA); lua_State* from = hookmgr::get_self(L)->coroutine_from((lua_State*)luadbg_touserdata(L, 1)); @@ -769,7 +815,23 @@ static int coroutine_from(luadbg_State* L) { luadbg_pushlightuserdata(L, from); return 1; } -#endif +static int coroutine_setparent(luadbg_State* L) { + luadbgL_checktype(L, 1, LUA_TLIGHTUSERDATA); + lua_State* co = (lua_State*)luadbg_touserdata(L, 1); + lua_State* parent = nullptr; + int t = luadbg_type(L, 2); + if (t != LUADBG_TNIL && t != LUADBG_TNONE) { + luadbgL_checktype(L, 2, LUA_TLIGHTUSERDATA); + parent = (lua_State*)luadbg_touserdata(L, 2); + } + hookmgr::get_self(L)->coroutine_setparent(co, parent); + return 0; +} +static int coroutine_dead(luadbg_State* L) { + luadbgL_checktype(L, 1, LUA_TLIGHTUSERDATA); + hookmgr::get_self(L)->coroutine_dead((lua_State*)luadbg_touserdata(L, 1)); + return 0; +} LUADEBUG_FUNC int luaopen_luadebug_hookmgr(luadbg_State* L) { @@ -812,8 +874,10 @@ int luaopen_luadebug_hookmgr(luadbg_State* L) { #endif #if defined(LUA_HOOKTHREAD) { "thread_open", thread_open }, - { "coroutine_from", coroutine_from }, #endif + { "coroutine_from", coroutine_from }, + { "coroutine_setparent", coroutine_setparent }, + { "coroutine_dead", coroutine_dead }, { NULL, NULL }, }; luadbgL_setfuncs(L, lib, 1);