From 933fc7d1eb3fcbb83edee33ed1536105713a1125 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Wed, 16 Apr 2025 14:40:42 +0200 Subject: [PATCH 01/13] manage tables via gen_server, delete tables on process dead --- .../gen_servers/callback_tracing_server.ex | 100 +++++++++++++++--- lib/live_debugger/services/trace_service.ex | 80 ++++++-------- 2 files changed, 117 insertions(+), 63 deletions(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 8a88c7430..d8b4d8393 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -1,21 +1,46 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do @moduledoc """ - This gen_server is responsible for tracing the callbacks of the LiveView processes. + This gen_server is responsible for tracing callbacks and managing ETS tables. """ use GenServer require Logger - alias LiveDebugger.Services.TraceService - alias LiveDebugger.Services.ModuleDiscoveryService + alias LiveDebugger.Services.{ModuleDiscoveryService, ChannelService, TraceService} alias LiveDebugger.Structs.Trace - alias LiveDebugger.Services.System.ProcessService alias LiveDebugger.Utils.Callbacks, as: CallbackUtils alias LiveDebugger.Utils.PubSub, as: PubSubUtils + @ets_table_name :lvdbg_traces @callback_functions CallbackUtils.callbacks_functions() + @type table_refs() :: %{pid() => :ets.table()} + + ## API + + @doc """ + Returns ETS table reference. + It creates table if none is associated with given pid + """ + @spec table(pid :: pid()) :: :ets.table() + def table(pid) when is_pid(pid) do + GenServer.call(__MODULE__, {:get_or_create_table, pid}, 1000) + end + + @doc """ + If table for given `pid` exists it deletes it from ETS. + """ + @spec delete_table(pid :: pid()) :: :ok + def delete_table(pid) when is_pid(pid) do + GenServer.call(__MODULE__, {:delete_table, pid}, 1000) + + :ok + end + + ## GenServer + + @doc false def start_link(args \\ []) do GenServer.start_link(__MODULE__, args, name: __MODULE__) end @@ -25,12 +50,12 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do tracing_setup_delay = Application.get_env(:live_debugger, :tracing_setup_delay, 0) Process.send_after(self(), :setup_tracing, tracing_setup_delay) - {:ok, []} + {:ok, %{}} end @impl true - def handle_info(:setup_tracing, state) do - :dbg.tracer(:process, {&trace_handler/2, 0}) + def handle_info(:setup_tracing, table_refs) do + :dbg.tracer(:process, {&handle_trace/2, 0}) :dbg.p(:all, :c) all_modules = ModuleDiscoveryService.all_modules() @@ -50,16 +75,56 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do # We trace it to refresh the components tree :dbg.tp({Phoenix.LiveView.Diff, :delete_component, 2}, []) - {:noreply, state} + {:noreply, table_refs} + end + + def handle_info({:DOWN, _, :process, closed_pid, _}, table_refs) do + {_, table_refs} = delete_ets_table(closed_pid, table_refs) + + {:noreply, table_refs} + end + + @impl true + def handle_call({:get_or_create_table, pid}, _from, table_refs) do + if ref = Map.get(table_refs, pid) do + {:reply, ref, table_refs} + else + ref = create_ets_table() + Process.monitor(pid) + {:reply, ref, Map.put(table_refs, pid, ref)} + end + end + + def handle_call({:delete_table, pid}, _from, table_refs) do + {_, table_refs} = delete_ets_table(pid, table_refs) + {:reply, :ok, table_refs} + end + + @spec create_ets_table() :: :ets.table() + defp create_ets_table() do + :ets.new(@ets_table_name, [:ordered_set, :public]) + end + + @spec delete_ets_table(pid(), table_refs()) :: {boolean(), table_refs()} + defp delete_ets_table(pid, table_refs) do + case Map.pop(table_refs, pid) do + {nil, table_refs} -> + {false, table_refs} + + {ref, updated_table_refs} -> + :ets.delete(ref) + {true, updated_table_refs} + end end # This handler is heavy because of fetching state and we do not care for order because it is not displayed to user # Because of that we do it asynchronously to speed up tracer a bit # We do not persist this trace because it is not displayed to user - defp trace_handler({_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}}, n) do + @spec handle_trace(term(), n :: integer()) :: integer() + defp handle_trace({_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}}, n) do Task.start(fn -> with cid <- %Phoenix.LiveComponent.CID{cid: cid}, - {:ok, %{socket: socket}} <- ProcessService.state(pid), + {:ok, %{socket: socket}} <- ChannelService.state(pid), %{id: socket_id, transport_pid: transport_pid} <- socket, true <- is_pid(transport_pid), trace <- @@ -82,7 +147,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do # This handles callbacks created by user that will be displayed to user # It cannot be async because we care about order - defp trace_handler({_, pid, _, {module, fun, args}}, n) when fun in @callback_functions do + defp handle_trace({_, pid, _, {module, fun, args}}, n) when fun in @callback_functions do with trace <- Trace.new(n, module, fun, args, pid), true <- is_pid(trace.transport_pid), :ok <- persist_trace(trace) do @@ -92,15 +157,16 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do n - 1 end - defp trace_handler(trace, n) do + defp handle_trace(trace, n) do Logger.info("Ignoring unexpected trace: #{inspect(trace)}") n end - defp persist_trace(trace) do - trace.transport_pid - |> TraceService.ets_table_id(trace.socket_id) - |> TraceService.insert(trace.id, trace) + @spec persist_trace(Trace.t()) :: :ok | {:error, term()} + defp persist_trace(%Trace{pid: pid, id: id} = trace) do + pid + |> table() + |> TraceService.insert(id, trace) :ok rescue @@ -109,6 +175,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do {:error, err} end + @spec publish_trace(Trace.t()) :: :ok | {:error, term()} defp publish_trace(%Trace{} = trace) do do_publish(trace) :ok @@ -118,6 +185,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do {:error, err} end + @spec do_publish(Trace.t()) :: :ok defp do_publish(%{module: Phoenix.LiveView.Diff} = trace) do trace |> PubSubUtils.component_deleted_topic() diff --git a/lib/live_debugger/services/trace_service.ex b/lib/live_debugger/services/trace_service.ex index 0c7dec4e4..33c445b44 100644 --- a/lib/live_debugger/services/trace_service.ex +++ b/lib/live_debugger/services/trace_service.ex @@ -1,59 +1,46 @@ defmodule LiveDebugger.Services.TraceService do @moduledoc """ - This module provides functions that manages traces in the debugged application via ETS. - Created table is an ordered_set with non-positive integer keys. + """ alias LiveDebugger.Structs.Trace alias LiveDebugger.CommonTypes alias LiveDebugger.Structs.LvProcess + alias LiveDebugger.GenServers.CallbackTracingServer alias Phoenix.LiveComponent.CID - @id_prefix "lvdbg-traces" @default_limit 100 + @type ets_elem() :: {integer(), Trace.t()} + @type ets_continuation :: term() + @doc """ - Returns the ETS table id for the given socket id. + Returns reference for accessing ETS table for given process. """ - @spec ets_table_id(pid(), String.t()) :: :ets.table() - def ets_table_id(transport_pid, socket_id) do - String.to_atom("#{@id_prefix}-#{inspect(transport_pid)}-#{socket_id}") - end - - @spec ets_table_id(LvProcess.t()) :: :ets.table() - def ets_table_id(%LvProcess{transport_pid: transport_pid, socket_id: socket_id}) do - ets_table_id(transport_pid, socket_id) + @spec ets_table(pid :: pid()) :: :ets.table() + def ets_table(pid) when is_pid(pid) do + CallbackTracingServer.table(pid) end - @doc """ - Initializes an ETS table with the given id if it doesn't exist. - """ - @spec maybe_init_ets(:ets.table()) :: :ets.table() - def maybe_init_ets(ets_table_id) do - if :ets.whereis(ets_table_id) == :undefined do - :ets.new(ets_table_id, [:ordered_set, :public, :named_table]) - else - ets_table_id - end + @spec ets_table(LvProcess.t()) :: :ets.table() + def ets_table(%LvProcess{pid: pid}) do + CallbackTracingServer.table(pid) end @doc """ Inserts a new trace into the ETS table. """ - @spec insert(:ets.table(), integer(), Trace.t()) :: true - def insert(table_id, id, trace) do - table_id - |> maybe_init_ets() - |> :ets.insert({id, trace}) + @spec insert(table :: :ets.table(), id :: integer(), Trace.t()) :: true + def insert(table, id, trace) when is_integer(id) do + :ets.insert(table, {id, trace}) end @doc """ Gets a trace from the ETS table by its id. """ - @spec get(:ets.table(), integer()) :: Trace.t() | nil - def get(table_id, id) do - table_id - |> maybe_init_ets() + @spec get(table :: :ets.table(), id :: integer()) :: Trace.t() | nil + def get(table, id) when is_integer(id) do + table |> :ets.lookup(id) |> case do [] -> nil @@ -70,13 +57,14 @@ defmodule LiveDebugger.Services.TraceService do * `:cont` - Used to get next page of items in the following queries * `:functions` - List of function names to filter traces by """ - @spec existing_traces(atom(), keyword()) :: {[Trace.t()], term()} | :end_of_table - def existing_traces(table_id, opts \\ []) do + @spec existing_traces(table :: :ets.table(), opts :: keyword()) :: + {[Trace.t()], ets_continuation()} | :end_of_table + def existing_traces(table, opts \\ []) do opts |> Keyword.get(:cont, nil) |> case do :end_of_table -> :end_of_table - nil -> existing_traces_start(table_id, opts) + nil -> existing_traces_start(table, opts) _cont -> existing_traces_continuation(opts) end |> case do @@ -94,20 +82,18 @@ defmodule LiveDebugger.Services.TraceService do @doc """ Deletes all traces for the given table id and CID or PID. """ - @spec clear_traces(atom(), pid() | CommonTypes.cid()) :: true - def clear_traces(table_id, %CID{} = cid) do - table_id - |> maybe_init_ets() - |> :ets.match_delete({:_, %{cid: cid}}) + @spec clear_traces(table :: :ets.table(), pid() | CommonTypes.cid()) :: true + def clear_traces(table, %CID{} = cid) do + :ets.match_delete(table, {:_, %{cid: cid}}) end - def clear_traces(table_id, pid) when is_pid(pid) do - table_id - |> maybe_init_ets() - |> :ets.match_delete({:_, %{pid: pid, cid: nil}}) + def clear_traces(table, pid) when is_pid(pid) do + :ets.match_delete(table, {:_, %{pid: pid, cid: nil}}) end - defp existing_traces_start(table_id, opts) do + @spec existing_traces_start(:ets.table(), Keyword.t()) :: + {[ets_elem()], ets_continuation()} | :"$end_of_table" + defp existing_traces_start(table, opts) do limit = Keyword.get(opts, :limit, @default_limit) functions = Keyword.get(opts, :functions, []) node_id = Keyword.get(opts, :node_id) @@ -118,11 +104,11 @@ defmodule LiveDebugger.Services.TraceService do match_spec = match_spec(node_id, functions) - table_id - |> maybe_init_ets() - |> :ets.select(match_spec, limit) + :ets.select(table, match_spec, limit) end + @spec existing_traces_continuation(Keyword.t()) :: + {[ets_elem()], ets_continuation()} | :"$end_of_table" defp existing_traces_continuation(opts) do cont = Keyword.get(opts, :cont, nil) From 9f309a57496e4956184c0b762e72b18bd1014198 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Wed, 16 Apr 2025 14:45:12 +0200 Subject: [PATCH 02/13] use ets_table instead of ets_table_id --- lib/live_debugger/live_views/traces_live.ex | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/live_debugger/live_views/traces_live.ex b/lib/live_debugger/live_views/traces_live.ex index 59376158e..ec41a8c26 100644 --- a/lib/live_debugger/live_views/traces_live.ex +++ b/lib/live_debugger/live_views/traces_live.ex @@ -68,7 +68,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do |> assign(node_id: node_id) |> assign(id: session["id"]) |> assign(root_pid: session["root_pid"]) - |> assign(ets_table_id: TraceService.ets_table_id(lv_process)) + |> assign(ets_table: TraceService.ets_table(lv_process.pid)) |> assign(lv_process: lv_process) |> TracingHelper.init() |> assign_async_existing_traces() @@ -290,10 +290,10 @@ defmodule LiveDebugger.LiveViews.TracesLive do @impl true def handle_event("clear-traces", _, socket) do - ets_table_id = socket.assigns.ets_table_id + ets_table = socket.assigns.ets_table node_id = socket.assigns.node_id - TraceService.clear_traces(ets_table_id, node_id) + TraceService.clear_traces(ets_table, node_id) socket |> stream(:existing_traces, [], reset: true) @@ -305,7 +305,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do def handle_event("open-trace", %{"data" => string_id}, socket) do trace_id = String.to_integer(string_id) - socket.assigns.ets_table_id + socket.assigns.ets_table |> TraceService.get(trace_id) |> case do nil -> @@ -323,7 +323,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do def handle_event("toggle-collapsible", %{"trace-id" => string_trace_id}, socket) do trace_id = String.to_integer(string_trace_id) - socket.assigns.ets_table_id + socket.assigns.ets_table |> TraceService.get(trace_id) |> case do nil -> @@ -348,7 +348,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do end defp assign_async_existing_traces(socket) do - ets_table_id = socket.assigns.ets_table_id + ets_table = socket.assigns.ets_table node_id = socket.assigns.node_id active_functions = get_active_functions(socket) @@ -356,7 +356,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do |> assign(:existing_traces_status, :loading) |> stream(:existing_traces, [], reset: true) |> start_async(:fetch_existing_traces, fn -> - TraceService.existing_traces(ets_table_id, + TraceService.existing_traces(ets_table, node_id: node_id, limit: @page_size, functions: active_functions @@ -365,7 +365,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do end defp load_more_existing_traces(socket) do - ets_table_id = socket.assigns.ets_table_id + ets_table = socket.assigns.ets_table node_id = socket.assigns.node_id cont = socket.assigns.traces_continuation active_functions = get_active_functions(socket) @@ -373,7 +373,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do socket |> assign(:traces_continuation, :loading) |> start_async(:load_more_existing_traces, fn -> - TraceService.existing_traces(ets_table_id, + TraceService.existing_traces(ets_table, node_id: node_id, limit: @page_size, cont: cont, From 148d12a1759851d2ed95987f600172e858990e97 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Wed, 16 Apr 2025 15:02:44 +0200 Subject: [PATCH 03/13] always check ets table before action --- .../gen_servers/callback_tracing_server.ex | 4 +- lib/live_debugger/live_views/traces_live.ex | 18 ++--- lib/live_debugger/services/trace_service.ex | 68 ++++++++++--------- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index d8b4d8393..55cbc7f3c 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -164,9 +164,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do @spec persist_trace(Trace.t()) :: :ok | {:error, term()} defp persist_trace(%Trace{pid: pid, id: id} = trace) do - pid - |> table() - |> TraceService.insert(id, trace) + TraceService.insert(pid, id, trace) :ok rescue diff --git a/lib/live_debugger/live_views/traces_live.ex b/lib/live_debugger/live_views/traces_live.ex index ec41a8c26..be2b84294 100644 --- a/lib/live_debugger/live_views/traces_live.ex +++ b/lib/live_debugger/live_views/traces_live.ex @@ -68,7 +68,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do |> assign(node_id: node_id) |> assign(id: session["id"]) |> assign(root_pid: session["root_pid"]) - |> assign(ets_table: TraceService.ets_table(lv_process.pid)) + |> assign(ets_table_id: lv_process.pid) |> assign(lv_process: lv_process) |> TracingHelper.init() |> assign_async_existing_traces() @@ -290,10 +290,10 @@ defmodule LiveDebugger.LiveViews.TracesLive do @impl true def handle_event("clear-traces", _, socket) do - ets_table = socket.assigns.ets_table + ets_table_id = socket.assigns.ets_table_id node_id = socket.assigns.node_id - TraceService.clear_traces(ets_table, node_id) + TraceService.clear_traces(ets_table_id, node_id) socket |> stream(:existing_traces, [], reset: true) @@ -305,7 +305,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do def handle_event("open-trace", %{"data" => string_id}, socket) do trace_id = String.to_integer(string_id) - socket.assigns.ets_table + socket.assigns.ets_table_id |> TraceService.get(trace_id) |> case do nil -> @@ -323,7 +323,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do def handle_event("toggle-collapsible", %{"trace-id" => string_trace_id}, socket) do trace_id = String.to_integer(string_trace_id) - socket.assigns.ets_table + socket.assigns.ets_table_id |> TraceService.get(trace_id) |> case do nil -> @@ -348,7 +348,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do end defp assign_async_existing_traces(socket) do - ets_table = socket.assigns.ets_table + ets_table_id = socket.assigns.ets_table_id node_id = socket.assigns.node_id active_functions = get_active_functions(socket) @@ -356,7 +356,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do |> assign(:existing_traces_status, :loading) |> stream(:existing_traces, [], reset: true) |> start_async(:fetch_existing_traces, fn -> - TraceService.existing_traces(ets_table, + TraceService.existing_traces(ets_table_id, node_id: node_id, limit: @page_size, functions: active_functions @@ -365,7 +365,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do end defp load_more_existing_traces(socket) do - ets_table = socket.assigns.ets_table + ets_table_id = socket.assigns.ets_table_id node_id = socket.assigns.node_id cont = socket.assigns.traces_continuation active_functions = get_active_functions(socket) @@ -373,7 +373,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do socket |> assign(:traces_continuation, :loading) |> start_async(:load_more_existing_traces, fn -> - TraceService.existing_traces(ets_table, + TraceService.existing_traces(ets_table_id, node_id: node_id, limit: @page_size, cont: cont, diff --git a/lib/live_debugger/services/trace_service.ex b/lib/live_debugger/services/trace_service.ex index 33c445b44..7d870a972 100644 --- a/lib/live_debugger/services/trace_service.ex +++ b/lib/live_debugger/services/trace_service.ex @@ -1,11 +1,11 @@ defmodule LiveDebugger.Services.TraceService do @moduledoc """ - + This module is responsible for accessing traces from ETS. + It uses calls to `CallbackTracingServer` to get proper table reference. """ alias LiveDebugger.Structs.Trace alias LiveDebugger.CommonTypes - alias LiveDebugger.Structs.LvProcess alias LiveDebugger.GenServers.CallbackTracingServer alias Phoenix.LiveComponent.CID @@ -13,34 +13,29 @@ defmodule LiveDebugger.Services.TraceService do @type ets_elem() :: {integer(), Trace.t()} @type ets_continuation :: term() - - @doc """ - Returns reference for accessing ETS table for given process. + @typedoc """ + Pid is used to store mapping to table references. + It identifies ETS tables managed by CallbackTracingServer """ - @spec ets_table(pid :: pid()) :: :ets.table() - def ets_table(pid) when is_pid(pid) do - CallbackTracingServer.table(pid) - end - - @spec ets_table(LvProcess.t()) :: :ets.table() - def ets_table(%LvProcess{pid: pid}) do - CallbackTracingServer.table(pid) - end + @type ets_table_id() :: pid() @doc """ Inserts a new trace into the ETS table. """ - @spec insert(table :: :ets.table(), id :: integer(), Trace.t()) :: true - def insert(table, id, trace) when is_integer(id) do - :ets.insert(table, {id, trace}) + @spec insert(table_id :: ets_table_id(), id :: integer(), Trace.t()) :: true + def insert(table_id, id, trace) when is_pid(table_id) and is_integer(id) do + table_id + |> ets_table() + |> :ets.insert({id, trace}) end @doc """ Gets a trace from the ETS table by its id. """ - @spec get(table :: :ets.table(), id :: integer()) :: Trace.t() | nil - def get(table, id) when is_integer(id) do - table + @spec get(table_id :: ets_table_id(), id :: integer()) :: Trace.t() | nil + def get(table_id, id) when is_pid(table_id) and is_integer(id) do + table_id + |> ets_table() |> :ets.lookup(id) |> case do [] -> nil @@ -57,14 +52,14 @@ defmodule LiveDebugger.Services.TraceService do * `:cont` - Used to get next page of items in the following queries * `:functions` - List of function names to filter traces by """ - @spec existing_traces(table :: :ets.table(), opts :: keyword()) :: + @spec existing_traces(table_id :: ets_table_id(), opts :: keyword()) :: {[Trace.t()], ets_continuation()} | :end_of_table - def existing_traces(table, opts \\ []) do + def existing_traces(table_id, opts \\ []) when is_pid(table_id) do opts |> Keyword.get(:cont, nil) |> case do :end_of_table -> :end_of_table - nil -> existing_traces_start(table, opts) + nil -> existing_traces_start(table_id, opts) _cont -> existing_traces_continuation(opts) end |> case do @@ -82,18 +77,22 @@ defmodule LiveDebugger.Services.TraceService do @doc """ Deletes all traces for the given table id and CID or PID. """ - @spec clear_traces(table :: :ets.table(), pid() | CommonTypes.cid()) :: true - def clear_traces(table, %CID{} = cid) do - :ets.match_delete(table, {:_, %{cid: cid}}) + @spec clear_traces(table_id :: ets_table_id(), pid() | CommonTypes.cid()) :: true + def clear_traces(table_id, %CID{} = cid) when is_pid(table_id) do + table_id + |> ets_table() + |> :ets.match_delete({:_, %{cid: cid}}) end - def clear_traces(table, pid) when is_pid(pid) do - :ets.match_delete(table, {:_, %{pid: pid, cid: nil}}) + def clear_traces(table_id, pid) when is_pid(table_id) and is_pid(pid) do + table_id + |> ets_table() + |> :ets.match_delete({:_, %{pid: pid, cid: nil}}) end - @spec existing_traces_start(:ets.table(), Keyword.t()) :: + @spec existing_traces_start(ets_table_id(), Keyword.t()) :: {[ets_elem()], ets_continuation()} | :"$end_of_table" - defp existing_traces_start(table, opts) do + defp existing_traces_start(table_id, opts) do limit = Keyword.get(opts, :limit, @default_limit) functions = Keyword.get(opts, :functions, []) node_id = Keyword.get(opts, :node_id) @@ -104,7 +103,9 @@ defmodule LiveDebugger.Services.TraceService do match_spec = match_spec(node_id, functions) - :ets.select(table, match_spec, limit) + table_id + |> ets_table() + |> :ets.select(match_spec, limit) end @spec existing_traces_continuation(Keyword.t()) :: @@ -143,4 +144,9 @@ defmodule LiveDebugger.Services.TraceService do [result] end + + @spec ets_table(table_id :: ets_table_id()) :: :ets.table() + defp ets_table(table_id) when is_pid(table_id) do + CallbackTracingServer.table(table_id) + end end From 2c0680b84192d47b4139739c93c720f292677a23 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Wed, 16 Apr 2025 15:17:38 +0200 Subject: [PATCH 04/13] broadcast process dead --- .../gen_servers/callback_tracing_server.ex | 4 ++++ lib/live_debugger/live_views/channel_dashboard_live.ex | 10 ++++++++-- lib/live_debugger/utils/pubsub.ex | 6 ++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 55cbc7f3c..9b5aa185d 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -81,6 +81,10 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do def handle_info({:DOWN, _, :process, closed_pid, _}, table_refs) do {_, table_refs} = delete_ets_table(closed_pid, table_refs) + closed_pid + |> PubSubUtils.process_status_topic() + |> PubSubUtils.broadcast({:process_status, :dead}) + {:noreply, table_refs} end diff --git a/lib/live_debugger/live_views/channel_dashboard_live.ex b/lib/live_debugger/live_views/channel_dashboard_live.ex index 1fc4f92d9..f8bc1e953 100644 --- a/lib/live_debugger/live_views/channel_dashboard_live.ex +++ b/lib/live_debugger/live_views/channel_dashboard_live.ex @@ -115,7 +115,7 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do end def handle_async(:fetch_lv_process, {:ok, fetched_lv_process}, socket) do - Process.monitor(fetched_lv_process.pid) + subscribe_process_state(fetched_lv_process.pid) socket |> assign(:lv_process, AsyncResult.ok(fetched_lv_process)) @@ -138,7 +138,7 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do end @impl true - def handle_info({:DOWN, _, :process, _closed_pid, _}, socket) do + def handle_info({:process_status, :dead}, socket) do socket |> push_patch(to: URL.remove_query_param(socket.assigns.url, "node_id")) |> start_async_assign_lv_process(%{"socket_id" => socket.assigns.socket_id}) @@ -214,4 +214,10 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do push_patch(socket, to: url) end + + defp subscribe_process_state(pid) do + pid + |> PubSubUtils.process_status_topic() + |> PubSubUtils.subscribe!() + end end diff --git a/lib/live_debugger/utils/pubsub.ex b/lib/live_debugger/utils/pubsub.ex index 61d013a72..64d1163c1 100644 --- a/lib/live_debugger/utils/pubsub.ex +++ b/lib/live_debugger/utils/pubsub.ex @@ -3,6 +3,7 @@ defmodule LiveDebugger.Utils.PubSub do This module provides helpers for LiveDebugger's PubSub. """ + alias LiveDebugger.Utils.Parsers alias LiveDebugger.Structs.Trace alias LiveDebugger.Structs.TreeNode @@ -58,6 +59,11 @@ defmodule LiveDebugger.Utils.PubSub do "lvdbg/#{inspect(transport_pid)}/#{socket_id}/component_deleted" end + @spec process_status_topic(pid :: pid()) :: String.t() + def process_status_topic(pid) when is_pid(pid) do + "lvdbg/#{inspect(pid)}/status" + end + @doc """ It stands for transport_pid/socket_id/node_id/function. From 2dca871adf3e8649e36503821c019ae0fb5116c0 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Wed, 16 Apr 2025 15:20:30 +0200 Subject: [PATCH 05/13] delete unused alias --- lib/live_debugger/utils/pubsub.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/live_debugger/utils/pubsub.ex b/lib/live_debugger/utils/pubsub.ex index 64d1163c1..63de6cc7f 100644 --- a/lib/live_debugger/utils/pubsub.ex +++ b/lib/live_debugger/utils/pubsub.ex @@ -3,7 +3,6 @@ defmodule LiveDebugger.Utils.PubSub do This module provides helpers for LiveDebugger's PubSub. """ - alias LiveDebugger.Utils.Parsers alias LiveDebugger.Structs.Trace alias LiveDebugger.Structs.TreeNode From 538936b56e90df9c33c72132cdea0c68b0b67882 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 10:07:10 +0200 Subject: [PATCH 06/13] add ping for tests --- .../gen_servers/callback_tracing_server.ex | 12 ++++++++++++ test/live_debugger/channel_dashboard_test.exs | 2 ++ 2 files changed, 14 insertions(+) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 9b5aa185d..cd002fc98 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -38,6 +38,14 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do :ok end + @doc """ + Checks if GenServer has been loaded + """ + @spec ping() :: :ok + def ping() do + GenServer.call(__MODULE__, :ping) + end + ## GenServer @doc false @@ -104,6 +112,10 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do {:reply, :ok, table_refs} end + def handle_call(:ping, _from, state) do + {:reply, :ok, state} + end + @spec create_ets_table() :: :ets.table() defp create_ets_table() do :ets.new(@ets_table_name, [:ordered_set, :public]) diff --git a/test/live_debugger/channel_dashboard_test.exs b/test/live_debugger/channel_dashboard_test.exs index 8ce2816ff..5443c96e0 100644 --- a/test/live_debugger/channel_dashboard_test.exs +++ b/test/live_debugger/channel_dashboard_test.exs @@ -5,6 +5,8 @@ defmodule LiveDebugger.ChannelDashboardTest do feature "user can see traces of executed callbacks and updated assigns", %{ sessions: [dev_app, debugger] } do + LiveDebugger.GenServers.CallbackTracingServer.ping() + dev_app |> visit(@dev_app_url) From 669e9f1e04391ec0a9a6875f513418e9426dcccd Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 11:34:29 +0200 Subject: [PATCH 07/13] change aliasing method --- lib/live_debugger/gen_servers/callback_tracing_server.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index cd002fc98..1401521df 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -7,7 +7,9 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do require Logger - alias LiveDebugger.Services.{ModuleDiscoveryService, ChannelService, TraceService} + alias LiveDebugger.Services.ModuleDiscoveryService + alias LiveDebugger.Services.ChannelService + alias LiveDebugger.Services.TraceService alias LiveDebugger.Structs.Trace alias LiveDebugger.Utils.Callbacks, as: CallbackUtils alias LiveDebugger.Utils.PubSub, as: PubSubUtils From 8234fea45749a5d7cf2d31750cb0309c2d3359b4 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 11:48:44 +0200 Subject: [PATCH 08/13] add impl --- lib/live_debugger/gen_servers/callback_tracing_server.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 1401521df..3ee84e0bd 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -88,6 +88,7 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do {:noreply, table_refs} end + @impl true def handle_info({:DOWN, _, :process, closed_pid, _}, table_refs) do {_, table_refs} = delete_ets_table(closed_pid, table_refs) @@ -109,11 +110,13 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do end end + @impl true def handle_call({:delete_table, pid}, _from, table_refs) do {_, table_refs} = delete_ets_table(pid, table_refs) {:reply, :ok, table_refs} end + @impl true def handle_call(:ping, _from, state) do {:reply, :ok, state} end From 1734e3b383073ec5ddcd4892e672886f8b405867 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 12:05:17 +0200 Subject: [PATCH 09/13] use case, use only trace when inserting --- .../gen_servers/callback_tracing_server.ex | 18 ++++++++++-------- lib/live_debugger/services/trace_service.ex | 15 ++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 3ee84e0bd..1f5c829e4 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -101,12 +101,14 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do @impl true def handle_call({:get_or_create_table, pid}, _from, table_refs) do - if ref = Map.get(table_refs, pid) do - {:reply, ref, table_refs} - else - ref = create_ets_table() - Process.monitor(pid) - {:reply, ref, Map.put(table_refs, pid, ref)} + case Map.get(table_refs, pid) do + nil -> + ref = create_ets_table() + Process.monitor(pid) + {:reply, ref, Map.put(table_refs, pid, ref)} + + ref -> + {:reply, ref, table_refs} end end @@ -184,8 +186,8 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do end @spec persist_trace(Trace.t()) :: :ok | {:error, term()} - defp persist_trace(%Trace{pid: pid, id: id} = trace) do - TraceService.insert(pid, id, trace) + defp persist_trace(%Trace{} = trace) do + TraceService.insert(trace) :ok rescue diff --git a/lib/live_debugger/services/trace_service.ex b/lib/live_debugger/services/trace_service.ex index 7d870a972..8c3e5d294 100644 --- a/lib/live_debugger/services/trace_service.ex +++ b/lib/live_debugger/services/trace_service.ex @@ -22,15 +22,16 @@ defmodule LiveDebugger.Services.TraceService do @doc """ Inserts a new trace into the ETS table. """ - @spec insert(table_id :: ets_table_id(), id :: integer(), Trace.t()) :: true - def insert(table_id, id, trace) when is_pid(table_id) and is_integer(id) do - table_id + @spec insert(Trace.t()) :: true + def insert(%Trace{pid: pid, id: id} = trace) do + pid |> ets_table() |> :ets.insert({id, trace}) end @doc """ - Gets a trace from the ETS table by its id. + Gets a trace from the ETS table by id. + It uses table associated with given PID. """ @spec get(table_id :: ets_table_id(), id :: integer()) :: Trace.t() | nil def get(table_id, id) when is_pid(table_id) and is_integer(id) do @@ -145,8 +146,8 @@ defmodule LiveDebugger.Services.TraceService do [result] end - @spec ets_table(table_id :: ets_table_id()) :: :ets.table() - defp ets_table(table_id) when is_pid(table_id) do - CallbackTracingServer.table(table_id) + @spec ets_table(pid :: ets_table_id()) :: :ets.table() + defp ets_table(pid) when is_pid(pid) do + CallbackTracingServer.table(pid) end end From c4e31c770862383b933b670e4edcdc6e0969f46f Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 12:29:22 +0200 Subject: [PATCH 10/13] better naming --- lib/live_debugger/services/trace_service.ex | 35 +++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/live_debugger/services/trace_service.ex b/lib/live_debugger/services/trace_service.ex index 8c3e5d294..27679c1e8 100644 --- a/lib/live_debugger/services/trace_service.ex +++ b/lib/live_debugger/services/trace_service.ex @@ -30,12 +30,11 @@ defmodule LiveDebugger.Services.TraceService do end @doc """ - Gets a trace from the ETS table by id. - It uses table associated with given PID. + Gets a trace of process from the ETS table by `id`. """ - @spec get(table_id :: ets_table_id(), id :: integer()) :: Trace.t() | nil - def get(table_id, id) when is_pid(table_id) and is_integer(id) do - table_id + @spec get(pid :: ets_table_id(), id :: integer()) :: Trace.t() | nil + def get(pid, id) when is_pid(pid) and is_integer(id) do + pid |> ets_table() |> :ets.lookup(id) |> case do @@ -45,7 +44,7 @@ defmodule LiveDebugger.Services.TraceService do end @doc """ - Returns existing traces for the given table id with optional filters. + Returns existing traces of a process for the table with optional filters. ## Options * `:node_id` - PID or CID to filter traces by @@ -53,14 +52,14 @@ defmodule LiveDebugger.Services.TraceService do * `:cont` - Used to get next page of items in the following queries * `:functions` - List of function names to filter traces by """ - @spec existing_traces(table_id :: ets_table_id(), opts :: keyword()) :: + @spec existing_traces(pid :: ets_table_id(), opts :: keyword()) :: {[Trace.t()], ets_continuation()} | :end_of_table - def existing_traces(table_id, opts \\ []) when is_pid(table_id) do + def existing_traces(pid, opts \\ []) when is_pid(pid) do opts |> Keyword.get(:cont, nil) |> case do :end_of_table -> :end_of_table - nil -> existing_traces_start(table_id, opts) + nil -> existing_traces_start(pid, opts) _cont -> existing_traces_continuation(opts) end |> case do @@ -76,19 +75,21 @@ defmodule LiveDebugger.Services.TraceService do end @doc """ - Deletes all traces for the given table id and CID or PID. + Deletes traces for LiveView or LiveComponent for given pid. + + * `node_id` - PID or CID which identifies node """ - @spec clear_traces(table_id :: ets_table_id(), pid() | CommonTypes.cid()) :: true - def clear_traces(table_id, %CID{} = cid) when is_pid(table_id) do - table_id + @spec clear_traces(pid :: ets_table_id(), node_id :: pid() | CommonTypes.cid()) :: true + def clear_traces(pid, %CID{} = node_id) when is_pid(pid) do + pid |> ets_table() - |> :ets.match_delete({:_, %{cid: cid}}) + |> :ets.match_delete({:_, %{cid: node_id}}) end - def clear_traces(table_id, pid) when is_pid(table_id) and is_pid(pid) do - table_id + def clear_traces(pid, node_id) when is_pid(pid) and is_pid(node_id) do + pid |> ets_table() - |> :ets.match_delete({:_, %{pid: pid, cid: nil}}) + |> :ets.match_delete({:_, %{pid: node_id, cid: nil}}) end @spec existing_traces_start(ets_table_id(), Keyword.t()) :: From e51014fbe5a9dbe68f83e5cc49ae8f75be3d4b76 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 12:32:13 +0200 Subject: [PATCH 11/13] remove unnecessary assign --- lib/live_debugger/live_views/traces_live.ex | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/live_debugger/live_views/traces_live.ex b/lib/live_debugger/live_views/traces_live.ex index be2b84294..d2e7066b5 100644 --- a/lib/live_debugger/live_views/traces_live.ex +++ b/lib/live_debugger/live_views/traces_live.ex @@ -68,7 +68,6 @@ defmodule LiveDebugger.LiveViews.TracesLive do |> assign(node_id: node_id) |> assign(id: session["id"]) |> assign(root_pid: session["root_pid"]) - |> assign(ets_table_id: lv_process.pid) |> assign(lv_process: lv_process) |> TracingHelper.init() |> assign_async_existing_traces() @@ -290,10 +289,10 @@ defmodule LiveDebugger.LiveViews.TracesLive do @impl true def handle_event("clear-traces", _, socket) do - ets_table_id = socket.assigns.ets_table_id + pid = socket.assigns.lv_process.pid node_id = socket.assigns.node_id - TraceService.clear_traces(ets_table_id, node_id) + TraceService.clear_traces(pid, node_id) socket |> stream(:existing_traces, [], reset: true) @@ -305,7 +304,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do def handle_event("open-trace", %{"data" => string_id}, socket) do trace_id = String.to_integer(string_id) - socket.assigns.ets_table_id + socket.assigns.lv_process.pid |> TraceService.get(trace_id) |> case do nil -> @@ -323,7 +322,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do def handle_event("toggle-collapsible", %{"trace-id" => string_trace_id}, socket) do trace_id = String.to_integer(string_trace_id) - socket.assigns.ets_table_id + socket.assigns.lv_process.pid |> TraceService.get(trace_id) |> case do nil -> @@ -348,7 +347,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do end defp assign_async_existing_traces(socket) do - ets_table_id = socket.assigns.ets_table_id + pid = socket.assigns.lv_process.pid node_id = socket.assigns.node_id active_functions = get_active_functions(socket) @@ -356,7 +355,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do |> assign(:existing_traces_status, :loading) |> stream(:existing_traces, [], reset: true) |> start_async(:fetch_existing_traces, fn -> - TraceService.existing_traces(ets_table_id, + TraceService.existing_traces(pid, node_id: node_id, limit: @page_size, functions: active_functions @@ -365,7 +364,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do end defp load_more_existing_traces(socket) do - ets_table_id = socket.assigns.ets_table_id + pid = socket.assigns.lv_process.pid node_id = socket.assigns.node_id cont = socket.assigns.traces_continuation active_functions = get_active_functions(socket) @@ -373,7 +372,7 @@ defmodule LiveDebugger.LiveViews.TracesLive do socket |> assign(:traces_continuation, :loading) |> start_async(:load_more_existing_traces, fn -> - TraceService.existing_traces(ets_table_id, + TraceService.existing_traces(pid, node_id: node_id, limit: @page_size, cont: cont, From 22a6390d3ba394dd830acc059bb65b8ca469dd2d Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 12:33:38 +0200 Subject: [PATCH 12/13] ok --- lib/live_debugger/gen_servers/callback_tracing_server.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 1f5c829e4..2267a8355 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -36,8 +36,6 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do @spec delete_table(pid :: pid()) :: :ok def delete_table(pid) when is_pid(pid) do GenServer.call(__MODULE__, {:delete_table, pid}, 1000) - - :ok end @doc """ From 6cdc2693fbc361db488392cb5c28057b90e3da21 Mon Sep 17 00:00:00 2001 From: Alan Guzek Date: Thu, 17 Apr 2025 13:23:26 +0200 Subject: [PATCH 13/13] use exclamation for functions which can timeout --- .../gen_servers/callback_tracing_server.ex | 12 ++++++------ lib/live_debugger/services/trace_service.ex | 16 ++++++++-------- test/live_debugger/channel_dashboard_test.exs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/live_debugger/gen_servers/callback_tracing_server.ex b/lib/live_debugger/gen_servers/callback_tracing_server.ex index 2267a8355..c68dc1a14 100644 --- a/lib/live_debugger/gen_servers/callback_tracing_server.ex +++ b/lib/live_debugger/gen_servers/callback_tracing_server.ex @@ -25,24 +25,24 @@ defmodule LiveDebugger.GenServers.CallbackTracingServer do Returns ETS table reference. It creates table if none is associated with given pid """ - @spec table(pid :: pid()) :: :ets.table() - def table(pid) when is_pid(pid) do + @spec table!(pid :: pid()) :: :ets.table() + def table!(pid) when is_pid(pid) do GenServer.call(__MODULE__, {:get_or_create_table, pid}, 1000) end @doc """ If table for given `pid` exists it deletes it from ETS. """ - @spec delete_table(pid :: pid()) :: :ok - def delete_table(pid) when is_pid(pid) do + @spec delete_table!(pid :: pid()) :: :ok + def delete_table!(pid) when is_pid(pid) do GenServer.call(__MODULE__, {:delete_table, pid}, 1000) end @doc """ Checks if GenServer has been loaded """ - @spec ping() :: :ok - def ping() do + @spec ping!() :: :ok + def ping!() do GenServer.call(__MODULE__, :ping) end diff --git a/lib/live_debugger/services/trace_service.ex b/lib/live_debugger/services/trace_service.ex index 27679c1e8..e115a4d02 100644 --- a/lib/live_debugger/services/trace_service.ex +++ b/lib/live_debugger/services/trace_service.ex @@ -25,7 +25,7 @@ defmodule LiveDebugger.Services.TraceService do @spec insert(Trace.t()) :: true def insert(%Trace{pid: pid, id: id} = trace) do pid - |> ets_table() + |> ets_table!() |> :ets.insert({id, trace}) end @@ -35,7 +35,7 @@ defmodule LiveDebugger.Services.TraceService do @spec get(pid :: ets_table_id(), id :: integer()) :: Trace.t() | nil def get(pid, id) when is_pid(pid) and is_integer(id) do pid - |> ets_table() + |> ets_table!() |> :ets.lookup(id) |> case do [] -> nil @@ -82,13 +82,13 @@ defmodule LiveDebugger.Services.TraceService do @spec clear_traces(pid :: ets_table_id(), node_id :: pid() | CommonTypes.cid()) :: true def clear_traces(pid, %CID{} = node_id) when is_pid(pid) do pid - |> ets_table() + |> ets_table!() |> :ets.match_delete({:_, %{cid: node_id}}) end def clear_traces(pid, node_id) when is_pid(pid) and is_pid(node_id) do pid - |> ets_table() + |> ets_table!() |> :ets.match_delete({:_, %{pid: node_id, cid: nil}}) end @@ -106,7 +106,7 @@ defmodule LiveDebugger.Services.TraceService do match_spec = match_spec(node_id, functions) table_id - |> ets_table() + |> ets_table!() |> :ets.select(match_spec, limit) end @@ -147,8 +147,8 @@ defmodule LiveDebugger.Services.TraceService do [result] end - @spec ets_table(pid :: ets_table_id()) :: :ets.table() - defp ets_table(pid) when is_pid(pid) do - CallbackTracingServer.table(pid) + @spec ets_table!(pid :: ets_table_id()) :: :ets.table() + defp ets_table!(pid) when is_pid(pid) do + CallbackTracingServer.table!(pid) end end diff --git a/test/live_debugger/channel_dashboard_test.exs b/test/live_debugger/channel_dashboard_test.exs index 5443c96e0..febcedef9 100644 --- a/test/live_debugger/channel_dashboard_test.exs +++ b/test/live_debugger/channel_dashboard_test.exs @@ -5,7 +5,7 @@ defmodule LiveDebugger.ChannelDashboardTest do feature "user can see traces of executed callbacks and updated assigns", %{ sessions: [dev_app, debugger] } do - LiveDebugger.GenServers.CallbackTracingServer.ping() + LiveDebugger.GenServers.CallbackTracingServer.ping!() dev_app |> visit(@dev_app_url)