riccardomanfrin

riccardomanfrin

Supervisor to DynamicSupervisor migration

Hi buddies,

in my project I was using Supervisor to start and terminate+delete children dynamically. Elixir complained I should be using a DynamicSupervisor:

warning: Supervisor.terminate_child/2 with a PID is deprecated, please use DynamicSupervisor instead
  (elixir 1.16.1) lib/supervisor.ex:1030: Supervisor.terminate_child/2

So I switched to that. Unfortunately the doc for DynamicSupervisor points out that the id of the child_spec, while required, is ignored:

Note that while the :id field is still required in the spec, the value is ignored and therefore does not need to be unique.

The effect of this is the following: with Supervisor.which_children I was able to see the child spec id of the child along with its pid:

iex(53)> Supervisor.which_children(MySup)
[
  {"foo_child-0012041214", #PID<0.509.0>, :worker, [ChildModule]}
]

Instead, with DynamicSupervisor, which_children api returns :undefined as the first element of the tuple:

iex(31)> DynamicSupervisor.which_children(MySup)
[{:undefined, #PID<0.270.0>, :worker, [ChildModule]}]

The problem is that I need to command the termination of children via external APIs, which don’t of course have notion of pids :slight_smile: .

With Supervisor I could directly read the child spec id. Do I now have to explicitly instrument the Registry boilerplate? is there some idiomatic shortcut to obtain a similar result?

Thanks

Marked As Solved

dimitarvp

dimitarvp

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).

Also Liked

D4no0

D4no0

Transient is a restart strategy that restarts the process only when the process crashes abnormally:

:transient - the child process is restarted only if it terminates abnormally, i.e., with an exit reason other than :normal, :shutdown, or {:shutdown, term}.

dimitarvp

dimitarvp

Because I am paranoid. Technically it should work if it’s only in either place. But don’t quote me on that, I say just try it.

mudasobwa

mudasobwa

Creator of Cure

The default for the GenServer is defined in use. The supervisor might override it.

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement