Skip to content

Commit 238c211

Browse files
committed
dont remove table when any process is watching
1 parent 5960ea0 commit 238c211

2 files changed

Lines changed: 89 additions & 29 deletions

File tree

lib/live_debugger/gen_servers/ets_table_server.ex

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,43 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
33
This gen_server is responsible for managing ETS tables.
44
"""
55

6+
defmodule TableInfo do
7+
defstruct [:table, alive?: true, watchers: MapSet.new()]
8+
9+
@type t() :: %__MODULE__{
10+
alive?: boolean(),
11+
table: :ets.table(),
12+
watchers: MapSet.t()
13+
}
14+
end
15+
616
use GenServer
717

18+
alias __MODULE__.TableInfo
819
alias LiveDebugger.Utils.PubSub, as: PubSubUtils
920

10-
@ets_table_name :lvdbg_traces
21+
@type state() :: %{pid() => TableInfo.t()}
1122

12-
@type table_refs() :: %{pid() => :ets.table()}
23+
@ets_table_name :lvdbg_traces
1324

1425
## API
1526

1627
@callback table(pid :: pid()) :: :ets.table()
17-
@callback delete_table(pid :: pid()) :: :ok
28+
@callback watch(pid :: pid()) :: :ok | {:error, term()}
1829

1930
@doc """
2031
Returns ETS table reference.
2132
It creates table if none is associated with given pid
2233
"""
23-
@spec table(pid :: pid()) :: :ets.table()
2434
def table(pid) when is_pid(pid), do: impl().table(pid)
2535

2636
@doc """
27-
If table for given `pid` exists it deletes it from ETS.
37+
Adds watcher to indicate when to delete table from ETS.
38+
It uses pid of process which the function was called.
2839
"""
29-
@spec delete_table(pid :: pid()) :: :ok
30-
def delete_table(pid) when is_pid(pid), do: impl().delete_table(pid)
40+
def watch(pid) when is_pid(pid) do
41+
impl().watch(pid)
42+
end
3143

3244
## GenServer
3345

@@ -41,33 +53,64 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
4153
{:ok, %{}}
4254
end
4355

56+
# This is for debugged processes monitored
4457
@impl true
45-
def handle_info({:DOWN, _, :process, closed_pid, _}, table_refs) do
46-
{_, table_refs} = delete_ets_table(closed_pid, table_refs)
58+
def handle_info({:DOWN, _, :process, closed_pid, _}, state)
59+
when is_map_key(state, closed_pid) do
60+
state =
61+
state
62+
|> Map.update!(closed_pid, fn table_info -> %{table_info | alive?: false} end)
63+
|> maybe_delete_ets_table(closed_pid)
4764

4865
PubSubUtils.process_status_topic()
4966
|> PubSubUtils.broadcast({:process_status, {:dead, closed_pid}})
5067

51-
{:noreply, table_refs}
68+
{:noreply, state}
69+
end
70+
71+
# This is for watchers processes
72+
@impl true
73+
def handle_info({:DOWN, _, :process, closed_pid, _}, state) do
74+
{updated_state, touched_pids} = remove_watcher(state, closed_pid)
75+
76+
updated_state =
77+
touched_pids
78+
|> Enum.reduce(updated_state, fn pid, state_acc ->
79+
maybe_delete_ets_table(state_acc, pid)
80+
end)
81+
82+
{:noreply, updated_state}
5283
end
5384

5485
@impl true
55-
def handle_call({:get_or_create_table, pid}, _from, table_refs) do
56-
case Map.get(table_refs, pid) do
86+
def handle_call({:get_or_create_table, pid}, _from, state) do
87+
case Map.get(state, pid) do
5788
nil ->
5889
ref = create_ets_table()
5990
Process.monitor(pid)
60-
{:reply, ref, Map.put(table_refs, pid, ref)}
91+
{:reply, ref, Map.put(state, pid, %TableInfo{table: ref})}
6192

62-
ref ->
63-
{:reply, ref, table_refs}
93+
%TableInfo{table: ref} ->
94+
{:reply, ref, state}
6495
end
6596
end
6697

6798
@impl true
68-
def handle_call({:delete_table, pid}, _from, table_refs) do
69-
{_, table_refs} = delete_ets_table(pid, table_refs)
70-
{:reply, :ok, table_refs}
99+
def handle_call({:watch, pid}, {watcher, _}, state) do
100+
case Map.get(state, pid) do
101+
%TableInfo{watchers: watchers} = table_info ->
102+
updated_watchers = MapSet.put(watchers, watcher)
103+
104+
updated_state =
105+
Map.put(state, pid, %{table_info | watchers: updated_watchers})
106+
107+
Process.monitor(watcher)
108+
109+
{:reply, :ok, updated_state}
110+
111+
_ ->
112+
{:reply, {:error, :process_not_found}, state}
113+
end
71114
end
72115

73116
defp impl() do
@@ -85,8 +128,8 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
85128
end
86129

87130
@impl true
88-
def delete_table(pid) do
89-
GenServer.call(@server_module, {:delete_table, pid}, 1000)
131+
def watch(pid) do
132+
GenServer.call(@server_module, {:watch, pid}, 1000)
90133
end
91134
end
92135

@@ -95,15 +138,30 @@ defmodule LiveDebugger.GenServers.EtsTableServer do
95138
:ets.new(@ets_table_name, [:ordered_set, :public])
96139
end
97140

98-
@spec delete_ets_table(pid(), table_refs()) :: {boolean(), table_refs()}
99-
defp delete_ets_table(pid, table_refs) do
100-
case Map.pop(table_refs, pid) do
101-
{nil, table_refs} ->
102-
{false, table_refs}
103-
104-
{ref, updated_table_refs} ->
105-
:ets.delete(ref)
106-
{true, updated_table_refs}
141+
@spec maybe_delete_ets_table(state(), pid()) :: state()
142+
defp maybe_delete_ets_table(state, pid) do
143+
with {%TableInfo{alive?: false} = table_info, updated_state} <- Map.pop(state, pid),
144+
true <- Enum.empty?(table_info.watchers) do
145+
:ets.delete(table_info.table)
146+
updated_state
147+
else
148+
_ ->
149+
state
107150
end
108151
end
152+
153+
@spec remove_watcher(state(), pid()) :: {state(), [pid()]}
154+
defp remove_watcher(state, watcher) when is_pid(watcher) do
155+
Enum.reduce(state, {state, []}, fn {pid, %{watchers: watchers} = table_info},
156+
{state_acc, pids} ->
157+
if Enum.member?(watchers, watcher) do
158+
updated_state =
159+
Map.put(state_acc, pid, %{table_info | watchers: MapSet.delete(watchers, watcher)})
160+
161+
{updated_state, [pid | pids]}
162+
else
163+
{state_acc, pids}
164+
end
165+
end)
166+
end
109167
end

lib/live_debugger_web/hooks/linked_view.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ defmodule LiveDebuggerWeb.Hooks.LinkedView do
6363
PubSubUtils.process_status_topic()
6464
|> PubSubUtils.subscribe!()
6565

66+
LiveDebugger.GenServers.EtsTableServer.watch(fetched_lv_process.pid)
67+
6668
socket
6769
|> assign(:lv_process, AsyncResult.ok(fetched_lv_process))
6870
|> halt()

0 commit comments

Comments
 (0)