I am not sure what’s the problem here? I have written a worker + a DynamicSupervisor
implementation that successfully commands children to stop, by user-given name.
The trick was to use a Registry
for name registration. Oh, and you can’t rely on Process.whereis
and DynamicSupervisor.which_children
for exact lookups, you have to use GenServer.whereis
.
Here’s what I have in files lying around, and I just tested it. Obviously change YYY
to your app namespace.
The worker:
# one_worker.ex
defmodule YYY.OneWorker do
use GenServer, restart: :transient
# Only restart if it exits abnormally; otherwise we'll be stopping these manually.
def start_link(request_id) do
GenServer.start_link(__MODULE__, request_id, name: via_tuple(request_id))
end
def child_spec(request_id) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [request_id]},
restart: :transient
}
end
def stop(request_id, stop_reason \\ :normal) do
# Given the :transient option in the child spec, the GenServer will restart
# if any reason other than `:normal` is given.
request_id |> via_tuple() |> GenServer.stop(stop_reason)
end
def ensure_started(request_id) do
case YYY.OneDynamicSupervisor.start_child(request_id) do
{:ok, _pid} -> :ok
{:error, {:already_started, _pid}} -> :ok
other -> raise other
end
end
def ping(request_id, text) do
ensure_started(request_id)
request_id |> via_tuple() |> GenServer.call({:ping, text})
end
@impl GenServer
def init(initial_state) do
IO.puts("initial_state=#{initial_state}")
{:ok, initial_state}
end
@impl GenServer
def handle_call({:ping, text}, _from, state) do
response = "#{inspect(state)}: #{text}"
IO.puts(response)
{:reply, response, state}
end
defp via_tuple(request_id) do
{:via, Registry, {:one_registry, request_id}}
end
end
The dynamic supervisor:
# one_dynamic_supervisor.ex
defmodule YYY.OneDynamicSupervisor do
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def start_child(request_id) do
# Shorthand to retrieve the child specification from the `child_spec/1` method of the given module.
child_spec = {YYY.OneWorker, request_id}
DynamicSupervisor.start_child(__MODULE__, child_spec)
end
@impl DynamicSupervisor
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
end
Also make sure to add those children to your Application.start
return value (i.e. the children of your app):
YYY.OneDynamicSupervisor
{Registry, [keys: :unique, name: :one_registry]}
You can see that I have included a function to stop a child (YYY.OneWorker.stop/1
). It uses a :via
tuple to locate and stop it. I have not included a function to locate a child’s PID but it’s as simple as this:
def whereis(request_id) do
{:via, Registry, {:one_registry, request_id}}
|> GenServer.whereis()
end
That should solve your problem.
Caveats and remarks:
- The
YYY.
namespace should be changed to your app’s;
- The
OneWorker
and OneDynamicSupervisor
names are placeholders, IMO change them before doing a GIT commit in your repo;
- The
:one_registry
name of the registry should be changed;
- Remove the
IO.puts
calls, I’ve put them just for demonstration;
request_id
is simply your user-supplied ID / name, feel free to change it to anything else (and it can be anything else besides an integer as well).