Julia VS Code debugger crash

I am trying to debug code in the julia vscode debugger. Usually all goes well but when setting a breakpoint inside a function, that is called by an imported library, the debugger crashes when trying to display the variables in the scope.

More specifically, in the vscode settings I set

    "julia.debuggerDefaultCompiled": [
        "ALL_MODULES_EXCEPT_MAIN",
        "-Lux.",
        "-Lux.Training",
        "-Lux.Training.compute_gradients",
    ]

and I try to debug the code where I use a custom loss function in some machine learning Lux code, where the loss function is called by the Lux-function, that computes gradients. For reference, here is a snippet of the code:

...

loss_function = function(model,ps,state,data)
    (x,y) = data
    y_p, state = model(x,ps,state)
    stats = []
    return mean((y_p.-y).^2), state, stats
end

...

_, loss, _, tstate = Training.single_train_step!(vjp, loss_function, data, tstate)

The code runs fine without debugger mode. However, even though the debugger jumps to the breakpoint, which is set at the line where you see stats = [], it directly thereafter crashes, when trying to display the variables in scope, with the following error:

"> Connecting to debugger… Done!

┌ Error: Some Julia code in the VS Code extension crashed
â”” @ VSCodeDebugger ~/.vscode-server/extensions/julialang.language-julia-1.79.2/scripts/error_handler.jl:15
ERROR: AssertionError: !(is_generated(method))
Stacktrace:
[1] compute_corrected_linerange
@ ~/.vscode-server/extensions/julialang.language-julia-1.79.2/scripts/packages/JuliaInterpreter/src/utils.jl:441 [inlined]
[2] scopes_request(conn::VSCodeDebugger.JSONRPC.JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint}, state::VSCodeDebugger.DebugAdapter.DebuggerState, params::VSCodeDebugger.DebugAdapter.ScopesArguments)
@ VSCodeDebugger.DebugAdapter ~/.vscode-server/extensions/julialang.language-julia-1.79.2/scripts/packages/DebugAdapter/src/debugger_requests.jl:561
[3] (::VSCodeDebugger.DebugAdapter.var"#129#155"{VSCodeDebugger.DebugAdapter.DebuggerState})(conn::VSCodeDebugger.JSONRPC.JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint}, params::VSCodeDebugger.DebugAdapter.ScopesArguments)
@ VSCodeDebugger.DebugAdapter ~/.vscode-server/extensions/julialang.language-julia-1.79.2/scripts/packages/DebugAdapter/src/packagedef.jl:56
[4] dispatch_msg(x::VSCodeDebugger.JSONRPC.JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint}, dispatcher::VSCodeDebugger.JSONRPC.MsgDispatcher, msg::Dict{String, Any})
@ VSCodeDebugger.JSONRPC ~/.vscode-server/extensions/julialang.language-julia-1.79.2/scripts/packages/JSONRPC/src/typed.jl:67
[5] (::VSCodeDebugger.DebugAdapter.var"#146#172"{VSCodeDebugger.var"#3#4"{Tuple{String, String}}, VSCodeDebugger.JSONRPC.JSONRPCEndpoint{Base.PipeEndpoint, Base.PipeEndpoint}, VSCodeDebugger.JSONRPC.MsgDispatcher})()
@ VSCodeDebugger.DebugAdapter ~/.vscode-server/extensions/julialang.language-julia-1.79.2/scripts/packages/DebugAdapter/src/packagedef.jl:76
"

Does anybody know why and how to resolve this? Thanks a lot!

I don’t know how to resolve this. Meanwhile you can try debugging using @show. For example,

@show stats
@show y_p

Hey! Thanks a lot @dqeeq for this tip. @show is helpful and works = ) but still, I would very much appreciate a working debugger. Any other comments or help would therefore be appreciated! = )

Anyone?

Providing a reproducible example would make it more likely for someone to be able to help you.

This is unreadable, please use a Markdown code block to properly display the error and stack trace.

Other things you could try:

  • report a bug to the VS Code extension
  • try an alternative debugger, I think Debugger.jl is an option

It too relies on JuliaInterpreter.jl under the hood though, so you’re likely to run into similar issues.

VS Code vendors its deps, so it’s theoretically possible for the JuliaInterpreter.jl version that VS Code uses to have a bug that’s already fixed when using Debugger.jl.

Following the error, I found an assertion check that prohibits the use of generated functions. (JuliaInterpreter.jl/src/utils.jl, line 447)
Lux.jl does have generated functions (e.g. line 93), so maybe one of them triggered the AssertionError? That would be my guess.
Since such an error would stem from JuliaInterpreter.jl, any debugging package that relies on it might have the same issue with generated functions.

Edit) Tried debugging a generated function, returns the same error.

@generated function foo(x)
    Core.println(x)
    return :(x * x)
end

foo(16)

┌ Error: Some Julia code in the VS Code extension crashed
â”” @ VSCodeDebugger 
~\.vscode\extensions\julialang.language-julia-1.124.2\scripts\error_handler.jl:15
ERROR: AssertionError: !(is_generated(method))
2 Likes

Thank you all for your responses!

Sorry, next time I will make the error message into a markdown block and add a reproducible example.

Thanks a lot @littlelib . Really nice that you discovered the cause of the problem!

But why does the debugger not work for generated functions and what can we do about it? Thanks again!

PS: I did open an issue as well here: Julia VS Code debugger crash · Issue #3704 · julia-vscode/julia-vscode · GitHub . I now added the minimal example of @littlelib there. Should I also open an issue at GitHub - JuliaDebug/JuliaInterpreter.jl: Interpreter for Julia code ?

2 Likes

Debugger.jl seems to be okay with @generated functions when tested on my machine, so I think it is a viable alternative.
I think the whole problem comes from JuliaInterpreter.compute_corrected_linerange function, which the Debugger.jl does not use.

Regarding the Julia-VSCode debugger, I think it’s for the best you leave it to the package developers. One must bypass JuliaInterpreter.compute_corrected_linerange, and they’re the only ones who can reimplement the relevant code, or give you instructions on some undocumented options that already do exactly that if they exist.

2 Likes

For what it’s worth, I’m getting the same behavior. It gets to a break point, sometimes I get the local variables to display, but if I type anything into the Debugger Console, it crashes immediately.

It doesn’t matter what code I use. I’ll try to put together a bug report tomorrow.

3 Likes

I found a workaround, that might be interesting for others, too. Often I use the debugger to execute a program until a certain point (that might be somewhere within some convoluted series of function calls) and then play around with the variables, and use this to continue writing the program from there or fix bugs.

For this, I figured that it would be enough to return variables with an exception, and then continue playing around with them in interactive mode, as if the program had stopped there.

I hence wrote a custom exception of the form

struct Breakpoint <: Exception
  vars::Any
end

Then somewhere in the code, where one wants to set the breakpoint, one can write

function f(x)
  out = []
  for i in x
    push!(out, 2i)
    throw(Breakpoint(Base.@locals))
  end
  out
end

and catch the exception with a macro, to get the variables one wants (for example those in the local scope via Base.@locals). A full working example looks like this:


struct Breakpoint <: Exception
  vars::Any
end
macro bp_vars(expr)
  return quote
    try
      $(esc(expr))
    catch e
      if e isa Breakpoint
        global bp_vars = e.vars
        println("Extracted variables at breakpoint")
        return
      else
          rethrow()
      end
    end
  end
end
function bp(vars)
  return throw(Breakpoint(vars))
end

function f(x)
  out = []
  for i in x
    push!(out, 2i)
    bp(Base.@locals)
  end
  out
end

@bp_vars f([2,3,4])
# Generates dictionary bp_vars

One can then unpack the variables if desired to have even the same names in the global scope, as follows:

using UnPack
function dynamic_unpack(dict)
    for (k, v) in dict
        @eval $(Symbol(k)) = $v
    end
end
dynamic_unpack(bp_vars)
# variables `x`, `out` and `i` are generated in global scope 

One can now continue writing the program in interactive mode, as if it had stopped at the “Breakpoint” bp.

2 Likes

That sounds a lot like what Infiltrator.jl does GitHub - JuliaDebug/Infiltrator.jl: No-overhead breakpoints in Julia

2 Likes

Yes, that’s true, I was able to mimic the behaviour of my custom exception using

# some code, after which you want your "interactive breakpoint"
@exfiltrate
@assert 1==0 "Exfiltrated variables"
# some other code

though this sometimes did not put all variables, that I wanted, into the safehouse, while with the custom exception, I was able to explicitly return them. And @infiltrate did not work in the function of the Lux-example, I mentioned at the beginning of the post. Nevertheless, Infiltrator.jl seems to be very cool = )

2 Likes