Skip to content

Commit ea250e2

Browse files
authored
Make debugger conform to DAP 1.51 continue/stepin/stepout/stepover (#678)
* remove not supported SetExceptionBreakpoints request handler Clients should only call this request if the capability ‘exceptionBreakpointFilters’ returns one or more filters. No way to implement it via :int module * implement function breakpoints * test erlang breakpoints * fix small memory leak when unsetting last breakpoint in file * add more breakpoint tests * make tests more synchronous * add function breakpoints tests * interpret modules * extract and test mfa parsing * run formatter * Update readme * cleanup * extract and test erlang binding processing fix some isses when var is usend more than 10 times add explicite ordering by variable instance discard underscored variables * breakpoint conditions support added do not warn when setting already set breakpoint * update readme * log when expression crashes * add support for conditional function breakpoints * update readme * add support for breakpoint hit condition * readme updated * implement log message * readme updated * add support for terminateThreads request * add support for pause * make continue, next, stepIn, stepout requests conform to DAP 1.51 Fixes #669 reworks fixes for #455 * cleanup :int workaround * tests wip * format
1 parent 6057d36 commit ea250e2

File tree

2 files changed

+64
-60
lines changed

2 files changed

+64
-60
lines changed

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 62 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,12 @@ defmodule ElixirLS.Debugger.Server do
186186
)
187187

188188
{thread_id, threads_inverse} = state.threads_inverse |> Map.pop(pid)
189-
state = remove_paused_process(state, pid)
189+
paused_processes = remove_paused_process(state, pid)
190190

191191
state = %{
192192
state
193193
| threads: state.threads |> Map.delete(thread_id),
194+
paused_processes: paused_processes,
194195
threads_inverse: threads_inverse
195196
}
196197

@@ -565,76 +566,48 @@ defmodule ElixirLS.Debugger.Server do
565566
{json, state}
566567
end
567568

568-
defp handle_request(continue_req(_, thread_id), state = %__MODULE__{}) do
569+
defp handle_request(continue_req(_, thread_id) = args, state = %__MODULE__{}) do
569570
pid = get_pid_by_thread_id!(state, thread_id)
570571

571-
try do
572-
:int.continue(pid)
573-
state = remove_paused_process(state, pid)
574-
{%{"allThreadsContinued" => false}, state}
575-
rescue
576-
e in MatchError ->
577-
raise ServerError,
578-
message: "serverError",
579-
format: ":int.continue failed: {message}",
580-
variables: %{
581-
"message" => inspect(Exception.message(e))
582-
}
583-
end
572+
safe_int_action(pid, :continue)
573+
574+
paused_processes = remove_paused_process(state, pid)
575+
paused_processes = maybe_continue_other_processes(args, paused_processes, pid)
576+
577+
processes_paused? = paused_processes |> Map.keys() |> Enum.any?(&is_pid/1)
578+
579+
{%{"allThreadsContinued" => not processes_paused?},
580+
%{state | paused_processes: paused_processes}}
584581
end
585582

586-
defp handle_request(next_req(_, thread_id), state = %__MODULE__{}) do
583+
defp handle_request(next_req(_, thread_id) = args, state = %__MODULE__{}) do
587584
pid = get_pid_by_thread_id!(state, thread_id)
588585

589-
try do
590-
:int.next(pid)
591-
state = remove_paused_process(state, pid)
592-
{%{}, state}
593-
rescue
594-
e in MatchError ->
595-
raise ServerError,
596-
message: "serverError",
597-
format: ":int.next failed: {message}",
598-
variables: %{
599-
"message" => inspect(Exception.message(e))
600-
}
601-
end
586+
safe_int_action(pid, :next)
587+
paused_processes = remove_paused_process(state, pid)
588+
589+
{%{},
590+
%{state | paused_processes: maybe_continue_other_processes(args, paused_processes, pid)}}
602591
end
603592

604-
defp handle_request(step_in_req(_, thread_id), state = %__MODULE__{}) do
593+
defp handle_request(step_in_req(_, thread_id) = args, state = %__MODULE__{}) do
605594
pid = get_pid_by_thread_id!(state, thread_id)
606595

607-
try do
608-
:int.step(pid)
609-
state = remove_paused_process(state, pid)
610-
{%{}, state}
611-
rescue
612-
e in MatchError ->
613-
raise ServerError,
614-
message: "serverError",
615-
format: ":int.stop failed: {message}",
616-
variables: %{
617-
"message" => inspect(Exception.message(e))
618-
}
619-
end
596+
safe_int_action(pid, :step)
597+
paused_processes = remove_paused_process(state, pid)
598+
599+
{%{},
600+
%{state | paused_processes: maybe_continue_other_processes(args, paused_processes, pid)}}
620601
end
621602

622-
defp handle_request(step_out_req(_, thread_id), state = %__MODULE__{}) do
603+
defp handle_request(step_out_req(_, thread_id) = args, state = %__MODULE__{}) do
623604
pid = get_pid_by_thread_id!(state, thread_id)
624605

625-
try do
626-
:int.finish(pid)
627-
state = remove_paused_process(state, pid)
628-
{%{}, state}
629-
rescue
630-
e in MatchError ->
631-
raise ServerError,
632-
message: "serverError",
633-
format: ":int.finish failed: {message}",
634-
variables: %{
635-
"message" => inspect(Exception.message(e))
636-
}
637-
end
606+
safe_int_action(pid, :finish)
607+
paused_processes = remove_paused_process(state, pid)
608+
609+
{%{},
610+
%{state | paused_processes: maybe_continue_other_processes(args, paused_processes, pid)}}
638611
end
639612

640613
defp handle_request(request(_, command), _state = %__MODULE__{}) when is_binary(command) do
@@ -646,6 +619,36 @@ defmodule ElixirLS.Debugger.Server do
646619
}
647620
end
648621

622+
defp maybe_continue_other_processes(%{"singleThread" => true}, paused_processes, requested_pid) do
623+
resumed_pids =
624+
for {paused_pid, %PausedProcess{ref: ref}} when paused_pid != requested_pid <-
625+
paused_processes do
626+
safe_int_action(paused_pid, :continue)
627+
true = Process.demonitor(ref, [:flush])
628+
paused_pid
629+
end
630+
631+
paused_processes |> Map.drop(resumed_pids)
632+
end
633+
634+
defp maybe_continue_other_processes(_, paused_processes, _requested_pid), do: paused_processes
635+
636+
# TODO consider removing this workaround as the problem seems to no longer affect OTP 24
637+
defp safe_int_action(pid, action) do
638+
apply(:int, action, [pid])
639+
:ok
640+
catch
641+
kind, payload ->
642+
# when stepping out of interpreted code a MatchError is risen inside :int module (at least in OTP 23)
643+
IO.warn(":int.#{action}(#{inspect(pid)}) failed: #{Exception.format(kind, payload)}")
644+
645+
unless action == :continue do
646+
safe_int_action(pid, :continue)
647+
end
648+
649+
:ok
650+
end
651+
649652
defp get_pid_by_thread_id!(state = %__MODULE__{}, thread_id) do
650653
case state.threads[thread_id] do
651654
nil ->
@@ -668,7 +671,7 @@ defmodule ElixirLS.Debugger.Server do
668671
true = Process.demonitor(process.ref, [:flush])
669672
end
670673

671-
%__MODULE__{state | paused_processes: paused_processes}
674+
paused_processes
672675
end
673676

674677
defp variables(state = %__MODULE__{}, pid, var, start, count, filter) do
@@ -948,6 +951,7 @@ defmodule ElixirLS.Debugger.Server do
948951
"supportsValueFormattingOptions" => false,
949952
"supportsExceptionInfoRequest" => false,
950953
"supportsTerminateThreadsRequest" => true,
954+
"supportsSingleThreadExecutionRequests" => true,
951955
"supportTerminateDebuggee" => false
952956
}
953957
end

apps/elixir_ls_debugger/test/debugger_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ defmodule ElixirLS.Debugger.ServerTest do
196196
1000
197197

198198
Server.receive_packet(server, continue_req(10, thread_id))
199-
assert_receive response(_, 10, "continue", %{"allThreadsContinued" => false})
199+
assert_receive response(_, 10, "continue", %{"allThreadsContinued" => true})
200200
end)
201201
end
202202

@@ -333,7 +333,7 @@ defmodule ElixirLS.Debugger.ServerTest do
333333
)
334334

335335
Server.receive_packet(server, continue_req(15, thread_id))
336-
assert_receive response(_, 15, "continue", %{"allThreadsContinued" => false})
336+
assert_receive response(_, 15, "continue", %{"allThreadsContinued" => true})
337337

338338
Server.receive_packet(server, stacktrace_req(7, thread_id))
339339
thread_id_str = inspect(thread_id)

0 commit comments

Comments
 (0)