I have DynamicSupervisor
for creating room-related process sub-trees per each room, where only 1 process in a sub-tree is actually needed from the outside:
RoomsDynamicSupervisor
| |
RoomSupervisor RoomSupervisor
| | | | |
ProcessB ProcessA ...
| | |
Task Task Task
ProcessA
will receive domain messages so it needs to be discoverable by clients (e.g. web controller) . ProcessB
manages auxiliary functionality which ProcessA
will be using internally. RoomSupervisor
, ProcessA
and ProcessB
are registered via Registry
, but when I create a room I really only need ProcessA
reference to use externally
RoomSupervisor
is created on demand, and I plan to hibernate them, and after some substantial inactivity period — to completely shut them down. So basically for each domain operation on ProcessA
I need to ensure it is actually started (along with RoomSupervisor
). For this I use code like this:
defmodule RoomsDynamicSupervisor do
# ...
@spec server_process(Room.id()) :: pid() | []
def server_process(room_id) do
existing_room_entry = ProcessRegistry.lookup(RoomSupervisor.registry_key(room_id))
if Enum.empty?(existing_room_entry) do
with {:ok, _} <- DynamicSupervisor.start_child(__MODULE__, {RoomSupervisor, room_id}),
[{process_a, _}] <- ProcessRegistry.lookup(ProcessA.registry_key(room_id)) do
process_a
end
else
with [{process_a, _}] <- ProcessRegistry.lookup(ProcessA.registry_key(room_id)) do
process_a
end
end
end
end
So when client will need to perform operation on ProcessA
, it will first need to get its pid using server_process(room_id)
, which is basically only needed to start the process if it is not started (otherwise I could just use via_tuple(room_id)
as the process is registered)
The question is, what is the optimal way to get ProcessA
pid? Options are:
- use
Registry
. If registry containsRoomSupervisor
corresponding forroom_id
, then we can assume itsProcessA
child is also started and we just look it up in the registry as well. Is this a viable strategy? My worries are thatProcessA
can be down or restarting meaning we can’t be precisely sure we get the pid, and if the registry lookup returns empty list it won’t be obvious what is the problem - use
RoomSupervisor.which_children
, then iterate over its 2 children to getProcessA
. It additionally will specifically state if the process is restarting. But here my worries are performance: will it be as fast (or similar) as registry lookup? The memory penalty noted in the docs is only relevant for large amount of children, but I only have 2 and I don’t see it will be much higher than that. But, I still need to iterate children
Edit: I like which_children
more due increased explicitness:
@spec server_process(Room.id()) :: Supervisor.child() | :restarting
def server_process(room_id) do
case ProcessRegistry.lookup(RoomSupervisor.registry_key(room_id)) do
[] -> DynamicSupervisor.start_child(__MODULE__, {RoomSupervisor, room_id}) |> elem(1)
[{process_a, _}] -> process_a
end
|> Supervisor.which_children()
|> Enum.find(fn {id, _, _, _} -> id == ProcessA end)
|> elem(1)
end