christhekeele

christhekeele

Understanding DynamicSupervisor & no initial children

I ran into a situation recently where it would be very useful to start up a DynamicSupervisor in an application’s sup tree, always with a specific list of child specs to boot on init—much the way normal Supervisors work, but with the option to dynamically start new children easily later.

I started writing a question on how to do so… Which turned into an experiment with handle_continue (since DynamicSupervisor is implemented as a GenServer)… Which turned into a half-written proposal to the core mailing list… Which turned into an incomplete proof-of-concept fork implementing support for this within Elixir… Before discovering that during the development of DynamicSupervisor, support for this was intentionally dropped because of technical difficulties expanding child specifications at boot time.

Problem is, I’m not sure I understand the original motivation or the difficulties in doing so. Based on my toy branch I had no difficultly using the existing validate_child logic to expand child specs given at init, so either the implementation of DynamicSupervisor has changed enough to overcome the obstacles present when it was first created, or (more likely) I’m missing something obvious that still prohibits it today.

(The implementation in my fork uses handle_continue when it doesn’t need to, and in fact shouldn’t, but that’s the circuitous route I took to get here. It’d be reworked in a proper PR.)

Does anyone have any insight into if and why we couldn’t pass DynamicSupervisor’s init procedure a list of child specs to boot at start? Perhaps you can explain the original rationale in a way I understand better? (Without spam @'ing them) do any of the original implementers mind chipping in? I’m reluctant to work more on a PR/open a feature proposal in the core mailing list without fully understanding why this was decided against originally.

Marked As Solved

josevalim

josevalim

Creator of Elixir

I would suggest to start a DynamicSupervisor with a Task under a rest_for_one supervisor. This way you can start children immediately after the supervisor boots. IIRC that was the main reason to keep the DynamicSupervisor API simpler, since this behaviour is not common and it can be easily replicated.

Also Liked

iautom8things

iautom8things

Just in case someone else (like me) happens upon this thread looking for guidance, but doesn’t quite grok what José is suggesting (also like me, initially):

I happened to find this great TIL repo by @slashdotdash that gives you a working example:

Thank you, everyone! :bowing_man:

Phillipp

Phillipp

I also have a DynamicSupervisor which needs to start some children on app startup.

For that I used the module based DynamicSupervisor so I can hook into the init function like that:

  def init(_arg) do
    Manager.subject_server_startup()
    DynamicSupervisor.init(strategy: :one_for_one)
  end

My Manager is in itself a GenServer, here are some snippets from my code:

  def subject_server_startup() do
    GenServer.cast(__MODULE__, :subjects_supervisor_startup)
  end

  def handle_cast(:subjects_supervisor_startup, state) do
    for subject <- config() do
      start_subject(subject)
    end

    {:noreply, state}
  end
edisonywh

edisonywh

I think it’s because of the restart strategy of the Task module, here’s the documentation:

https://hexdocs.pm/elixir/1.12/Task.html#module-statically-supervised-tasks

Opposite to GenServer, Agent and Supervisor, a Task has a default :restart of :temporary . This means the task will not be restarted even if it crashes. If you desire the task to be restarted for non-successful exits, do:

You can simulate this by creating a Task module yourself,

defmodule MyTask do
  require Logger
  # use Task, restart: :transient
  use Task

  def start_link(arg) do
    Task.start_link(__MODULE__, :run, [arg])
  end

  def run(arg) do
    Logger.info("Hello")
  end
end

Then put in in your supervision tree like normal {MyTask, []}. Kill the first process and you’ll see that MyTask doesn’t restart, then now try with the restart: :transient flag, and you’ll see it works

Linuus

Linuus

I tried this but it doesn’t seem to work? It just restarts my DynamicSupervisor and not my Task.
Is it because the Task has already finished so it can’t be “restarted”?

My Supervisor looks like this:

defmodule MyApp.ControllerSupervisor do
  use Supervisor

  require Logger

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  def init(_init_arg) do
    children = [
      {MyApp.DynamicControllerSupervisor, []},
      {Task,
       fn ->
         Logger.info("Task started")
         MyApp.DynamicControllerSupervisor.autostart_controller_connections
       end}
    ]

    Supervisor.init(children, strategy: :rest_for_one)
  end
end
christhekeele

christhekeele

Ooh, that’s better than how I was doing this, thanks!

One interesting thing I realized while tinkering around with implementing support for this is that doing so makes the behaviour contract for DynamicSupervisor match that of Supervisor (by returning a list of children + options in init/1).

I agree this behaviour is not common and most apps don’t need such a feature, but it does provide a compelling ‘upgrade path’ from a Supervisor to DynamicSupervisor: just replace the module name in use Supervisor and Supervisor.init, with the knowledge that any callback returns crafted by hand (instead of Supervisor.init) will continue to work since both callbacks now accept the same shape.

Then you could begin converting a Supervisor to a DynamicSupervisor with the knowledge that any children you were relying upon to be started initially in your supervision tree still will be as you gradually refactor how they are launched.

Of course, this is solving a problem I don’t think exists, I just find the parity and parallels pretty—agreed it’s probably not worth complicating the implementation for. :smile:

Where Next?

Popular in Questions Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
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
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
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
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New

We're in Beta

About us Mission Statement