Skip to content
Open
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 src/debugger_requests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# Request handlers

function initialize_request(debug_session::DebugSession, params::InitializeRequestArguments)
# The REPL-hosted debugger runs many sessions in one process (VSCodeServer's
# `start_debug_backend`), and JuliaInterpreter's breakpoint registry is process-global.
# The client only sends `setBreakpoints` for files that still have breakpoints, so a file
# whose breakpoints were all deleted between sessions is never mentioned again and its
# stale breakpoints would keep firing. Start every session from a clean slate; the client
# re-sends the full, current set during the configuration phase that follows.
# (Function breakpoints get the same treatment in `DebugEngines.set_function_breakpoints!`.)
for bp in copy(JuliaInterpreter.breakpoints())
bp isa JuliaInterpreter.BreakpointFileLocation && JuliaInterpreter.remove(bp)
end

return Capabilities(
true, # supportsConfigurationDoneRequest::Union{Missing,Bool}
true, # supportsFunctionBreakpoints::Union{Missing,Bool}
Expand Down Expand Up @@ -167,12 +178,16 @@ function set_break_points_request(debug_session::DebugSession, params::SetBreakp
@debug "setbreakpoints_request"

filename = params.source.path
# `JuliaInterpreter.breakpoint` normalizes the path it is handed and stores that, so the
# removal below has to compare against the normalized form or it silently matches nothing
# and leaves the old breakpoints in place.
normalized_filename = normpath(filename)

# JuliaInterpreter.remove mutates the vector returned by
# breakpoints(), so we make a copy to not mess up iteration
for bp in copy(JuliaInterpreter.breakpoints())
if bp isa JuliaInterpreter.BreakpointFileLocation
if bp.path == filename
if bp.path == normalized_filename
@debug "Removing breakpoint at $(bp.path):$(bp.line)"
JuliaInterpreter.remove(bp)
end
Expand Down
46 changes: 46 additions & 0 deletions test/test_debugsession.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,49 @@ end
@test DefaultTerminationTarget.ran == true
@test count(==("terminated"), events) == 1
end

@testitem "initialize clears file breakpoints left over from an earlier session" begin
import JuliaInterpreter

# The REPL-hosted debugger serves many sessions from one process, and the client only
# sends `setBreakpoints` for files that still have breakpoints. A file whose breakpoints
# were all deleted between sessions is therefore never mentioned again, so unless the
# adapter clears the process-global registry itself the deleted breakpoints keep firing
# for the rest of the REPL's life (julialang.org discourse #138981).
JuliaInterpreter.remove()

target = joinpath(@__DIR__, "stale_breakpoint_target.jl")
JuliaInterpreter.breakpoint(target, 2)
@test any(bp -> bp isa JuliaInterpreter.BreakpointFileLocation, JuliaInterpreter.breakpoints())

# A new session's first request, with no `setBreakpoints` following it.
session = DebugAdapter.DebugSession(IOBuffer())
DebugAdapter.initialize_request(session, DebugAdapter.InitializeRequestArguments(adapterID="julia"))

@test !any(bp -> bp isa JuliaInterpreter.BreakpointFileLocation, JuliaInterpreter.breakpoints())
end

@testitem "setBreakpoints removes previous breakpoints for a non-normalized source path" begin
import JuliaInterpreter

# `JuliaInterpreter.breakpoint` stores the normalized path, so the removal pass has to
# compare against the normalized form of `source.path` or it matches nothing and leaves
# the old breakpoints behind.
JuliaInterpreter.remove()

target = joinpath(@__DIR__, "sub", "..", "breakpoint_target.jl")
source = DebugAdapter.Source(path=target)

DebugAdapter.set_break_points_request(
DebugAdapter.DebugSession(IOBuffer()),
DebugAdapter.SetBreakpointsArguments(source=source, breakpoints=[DebugAdapter.SourceBreakpoint(line=2)])
)
@test count(bp -> bp isa JuliaInterpreter.BreakpointFileLocation, JuliaInterpreter.breakpoints()) == 1

# The client now reports that the file has no breakpoints left.
DebugAdapter.set_break_points_request(
DebugAdapter.DebugSession(IOBuffer()),
DebugAdapter.SetBreakpointsArguments(source=source, breakpoints=DebugAdapter.SourceBreakpoint[])
)
@test count(bp -> bp isa JuliaInterpreter.BreakpointFileLocation, JuliaInterpreter.breakpoints()) == 0
end
Loading