Skip to content

vscode: debugging fiber.new/fiber.create #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion debug-workspace/debug-dbg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,33 @@ for i = -1,#arg,1 do
print('arg['..i..'] = ' .. arg[i])
end
end

local function co_type()
return coroutine.running() and type(coroutine.running()) or ''
end

local function co_status()
return coroutine.running() and coroutine.status(coroutine.running()) or nil
end

print('1.out:', fiber.id())
print('1a.out', co_status())
print('1b.out', co_type())

local function fiber_function()
print('1.in:', "I'm a fiber")
print('2.in:', fiber.id())
print('2a.in', co_status())
print('2b.in', co_type())
fiber.yield()
fiber.sleep(10)
print('3.in:')
end
print('2.out:', fiber.id())

local fiber_object = fiber.create(fiber_function)
local fiber_object = fiber.new(fiber_function)
print('3.out:', "Fiber started")
fiber.yield()

local T = date.new{hour = 3, tzoffset = '+0300'}
print('4.out:', T)
Expand Down
118 changes: 108 additions & 10 deletions debugger/debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,32 @@ import {Breakpoint} from "./breakpoint";
import {Thread, mainThread, mainThreadName, isThread} from "./thread";

import * as tarantool from "tarantool";
import * as fiber from "fiber";
import type {LuaFiber} from "fiber";

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const luaTarantoolGetSources = tarantool?.debug?.getsources ?? function(filePath: string) {
return null;
};

const luaFiberCreate = fiber.create;
const luaFiberNew = fiber.new;
const luaFiberYield = fiber.yield;

const luaFiberId = fiber.id
const luaFiberStatus = fiber.status
const luaCoroStatus = coroutine.status
const luaCoroRunning = coroutine.running

const mainFiberId = luaFiberId()

// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export function isFiber(val: any): val is LuaFiber {
return val != null &&
type(val) === "userdata" &&
luaFiberId(val as LuaFiber) > 0;
}

export interface Var {
val: unknown;
type: string;
Expand Down Expand Up @@ -92,17 +112,22 @@ export namespace Debugger {
const hookStack: HookType[] = [];

const threadIds = setmetatable(new LuaTable<Thread, number | undefined>(), {__mode: "k"});
const fibers = setmetatable(new LuaTable<number, LuaFiber>(), {__mode: "kv"});
const threadStackOffsets = setmetatable(new LuaTable<Thread, number | undefined>(), {__mode: "k"});
const mainThreadId = 1;
threadIds.set(mainThread, mainThreadId);
let nextThreadId = mainThreadId + 1;

// calculate coroutine virtual id, or real fiber id
function getThreadId(thread: Thread) {
return luaAssert(threadIds.get(thread));
const threadId = threadIds.get(thread);
const fiberId = luaFiberId();
luaAssert(threadId || fiberId > mainFiberId);
return (threadId || fiberId);
}

function getActiveThread() {
return coroutine.running() ?? mainThread;
return luaCoroRunning() ?? mainThread;
}

function getLine(info: debug.FunctionInfo) {
Expand Down Expand Up @@ -499,13 +524,14 @@ export namespace Debugger {
breakInThread = undefined;
let frameOffset = activeThreadFrameOffset;
let frame = 0;
let currentThread = activeThread;
let currentThread: Thread | undefined = activeThread;
let currentStack = activeStack;
let info = luaAssert(currentStack[frame]);
let source = Path.format(luaAssert(info.source));
let sourceMap = SourceMap.get(source);
while (true) {
const inp = getInput();
print('**cmd**', inp);
if (!inp || inp === "quit") {
os.exit(0);

Expand Down Expand Up @@ -878,14 +904,13 @@ export namespace Debugger {
} else if (activeThread === breakInThread) {
stepBreak = getStack(debugHookStackOffset).length <= breakAtDepth;
} else {
stepBreak = breakInThread !== mainThread && coroutine.status(breakInThread as LuaThread) === "dead";
stepBreak = breakInThread !== mainThread && luaCoroStatus(breakInThread as LuaThread) === "dead";
}
if (stepBreak) {
const topFrameSource = debug.getinfo(debugHookStackOffset, "S");
if (!topFrameSource || !topFrameSource.source) {
return;
}

//Ignore debugger code
if (topFrameSource.source.sub(-debuggerName.length) === debuggerName) {
return;
Expand Down Expand Up @@ -1040,6 +1065,24 @@ export namespace Debugger {
return threadId;
}

function registerFiber(fiber_: LuaFiber | null) {
if (fiber_ === null) {
return null;
}
const fiberId = fiber.id(fiber_);
assert(!fibers.get(fiberId));
assert(isFiber(fiber_));

fibers.set(fiberId, fiber_);

const [hook] = debug.gethook();
if (hook === debugHook) {
debug.sethook(debugHook, "l");
}

return fiberId;
}

let canYieldAcrossPcall: boolean | undefined;

function useXpcallInCoroutine() {
Expand Down Expand Up @@ -1106,6 +1149,45 @@ export namespace Debugger {
return resumer;
}

/**
fiber.create() is a fiber_create() + fiber_start()
fiber.new() is a fiber_create() + fiber_wakeup()
We need to intercept control at the moment
we create fiber via fiber.new() [for registering it
in debugger threads list], but we have not way in Lua
to directly start fiber execution as fiber_start() is
**not** exported to Lua. But, at least we could yield.

The order of fiber switches will be messed entirely.
But who guaranteed it anyhow, anywhere>
*/
function debuggerFiberCreate(start : boolean) {
return function(f: Function, ...args: unknown[] ): LuaFiber | null {
const originalFunc = f as DebuggableFunction;
function debugFunc(...props: unknown[]) {
function wrappedFunc() {
return originalFunc(...props);
}
const results = xpcall(wrappedFunc, breakForError);
if (results[0]) {
return unpack(results, 2);
} else {
skipNextBreak = true;
const message = mapSources(tostring(results[1]));
return luaError(message, 2);
}
}
const fiber_ = luaFiberNew(debugFunc, ...args);
// print(fiber_)
//print(type(fiber_))
registerFiber(fiber_);
if (start) {
luaFiberYield(); // FIXME - it messes up order or execution
}
return fiber_;
}
}

//debug.traceback replacement for catching errors and mapping sources
function debuggerTraceback(
threadOrMessage?: LuaThread | string,
Expand Down Expand Up @@ -1165,17 +1247,19 @@ export namespace Debugger {
updateHook = function() {
if (breakAtDepth < 0 && Breakpoint.getCount() === 0) {
debug.sethook();
print('clearHook (globally)');

for (const [thread] of pairs(threadIds)) {
if (isThread(thread) && coroutine.status(thread) !== "dead") {
if (isThread(thread) && luaCoroStatus(thread) !== "dead") {
debug.sethook(thread);
}
}
} else {
debug.sethook(debugHook, "l");
print('set debugHook (globally)');

for (const [thread] of pairs(threadIds)) {
if (isThread(thread) && coroutine.status(thread) !== "dead") {
if (isThread(thread) && luaCoroStatus(thread) !== "dead") {
debug.sethook(thread, debugHook, "l");
}
}
Expand All @@ -1192,11 +1276,16 @@ export namespace Debugger {
coroutine.create = luaCoroutineCreate;
coroutine.wrap = luaCoroutineWrap;
coroutine.resume = luaCoroutineResume;
// FIXME - don't repeat after us! That is bad, really bad!
//@ts-ignore
fiber.create = luaFiberCreate;
//@ts-ignore
fiber.new = luaFiberNew;

debug.sethook();

for (const [thread] of pairs(threadIds)) {
if (isThread(thread) && coroutine.status(thread) !== "dead") {
if (isThread(thread) && luaCoroStatus(thread) !== "dead") {
debug.sethook(thread);
}
}
Expand All @@ -1218,9 +1307,18 @@ export namespace Debugger {
coroutine.create = (f: Function) => debuggerCoroutineCreate(f, breakInCoroutines);
coroutine.wrap = debuggerCoroutineWrap;
coroutine.resume = breakInCoroutines ? debuggerCoroutineResume : luaCoroutineResume;

const currentThread = coroutine.running();
// FIXME - don't repeat after us! That is bad, really bad!
//@ts-ignore
fiber.create = debuggerFiberCreate(true);
//@ts-ignore
fiber.new = debuggerFiberCreate(false);

const currentThread = luaCoroRunning();
// when coroutine.running() returns non-null that migt mean 2 things:
// - either we are in a real couroutine
// - or within running fiber
if (currentThread && !threadIds.get(currentThread)) {
print('register', currentThread);
registerThread(currentThread);
}

Expand Down
14 changes: 14 additions & 0 deletions debugger/fiber.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module "fiber" {
interface LuaFiber extends LuaUserData {
id : number;
}

const new_: (this: void, f: Function, ...args: unknown[]) => LuaFiber | null;
const create: (this: void, f: Function, ...args: unknown[]) => LuaFiber | null;
const yield: (this: void) => void;
const id: (this: void, fib?: LuaFiber | null) => number;
const status: (this: void, fib?: LuaFiber | null) => "running" | "dead" | "suspended" | null;

export { new_ as new, create, yield, id, status };
export type { LuaFiber };
}
2 changes: 1 addition & 1 deletion debugger/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"luaBundle": "lldebugger.lua",
"luaBundleEntry": "lldebugger.ts",
"luaLibImport": "require",
"noResolvePaths": ["tarantool"]
"noResolvePaths": ["tarantool", "fiber"]
}
}