@@ -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
109167end
0 commit comments