EDIT: References to Chapter 9 emended to Chapter 10
Wanted to post this for those working on Chapter 10’s exercise.
I did not like my initial answer to Chapter 10’s exercise with respect to the :ets implementation for simple registries.
I did not understand why using Process.flag in the register/1 function prior to :ets.new_entry would result in exits being captured in a way that could be handled by the server process as per @sasajuric delineation (I’m still not sure on that facet btw). However, my inital alternative to that was to keep the Process.flag in init and have the module call a GenServer.cast @mod, {:register, pid}, having the resultant handle_cast call Process.link pid.
I felt like this may have created a bottleneck, though an ostensibly wide one, out of the registry process, since it was once again burdened with some degree of registration processing.
I could not think of how else to get the process to handle :EXIT messages, I wanted to link the Processes from the client end using Process.link(Process.whereis(@mod)) within the ETSR.register/1 function, but this was ineffective at first, which was not surprising as this just linked the registry process to the caller…and I was calling this from the shell!
Handles exits by ets registered process, but requires some handling of registrations by GenServer:
defmodule ETSR do # (ETSR is short for ets registry)
use GenServer
@mod __MODULE__
def start() do
GenServer.start(@mod, nil, name: @mod)
end
def register({pid, name}) do
case Process.alive?(pid) do
true ->
GenServer.cast(@mod, {:register, pid})
:ets.insert_new(@mod, {name, pid})
_ ->
IO.puts("Process not active")
end
end
def whereis(name) do
:ets.lookup(@mod, name)
end
@impl GenServer
def init(_) do
:ets.new(@mod, [:named_table, :public])
Process.flag(:trap_exit, true)
{:ok, %{}}
end
@impl GenServer
def handle_cast({:register, pid}, _) do
Process.link(pid)
{:noreply, nil}
end
@impl GenServer
def handle_info({:EXIT, pid, _reason}, state) do
:ets.match_delete(@mod, {:_, pid})
{:noreply, state}
end
end
Then, finally and thankfully, the truth of the exercise dawned on me.
The process itself is to call the ETSR.register({pid, :name}), rather than the call being made from the shell. I’m not sure why I thought that these calls were to be made from the shell, but I’m thankful to have understood the question properly now.
Working implementation without bottleneck:
defmodule ETSR do
use GenServer
@mod __MODULE__
def start() do
GenServer.start(@mod, nil, name: @mod)
end
def register({pid, name}) do
case Process.alive?(pid) do
true ->
Process.whereis(@mod)
|> Process.link
:ets.insert_new(@mod, {name, pid})
_ ->
IO.puts("Process not active")
end
end
def whereis(name) do
case :ets.lookup(@mod, name) do
[] -> nil
result -> result
end
end
@impl GenServer
def init(_) do
:ets.new(@mod, [:named_table, :public])
Process.flag(:trap_exit, true)
{:ok, %{}}
end
@impl GenServer
def handle_info({:EXIT, pid, _reason}, state) do
:ets.match_delete(@mod, {:_, pid})
{:noreply, state}
end
end
Still not entirely sure why we are to place and Process.flag(:trap_exit, true) in the register/1 function, when I tried that approach (probably incorrectly) the exits were not being handled.
This is the test I performed on the working version:
iex(1)> ETSR.start
{:ok, #PID<0.116.0>}
iex(2)> pid1 = spawn(fn -> ETSR.register({self(), :proc1})
...(2)> Process.sleep 10_000
...(2)> end)
#PID<0.117.0>
iex(3)> ETSR.whereis :proc1
[proc1: #PID<0.117.0>]
iex(4)> ETSR.whereis :proc1
nil #After 10 seconds
Just wanted to post this in case anyone is pulling their hair out over that exercise due to a silly misunderstanding.
I take pid and match to pid1 just so I can check Process.alive? pid1, however, I did not need to. I may try the benchmarks later BGG.






















