How to add a children to 'Supervisor.start_link' dynamically?

In my phoenix app in application.ex I have this:

def start(_type, _args) do
  import Supervisor.Spec

# TODO
children = [
  supervisor(MyApp123.Module1, []),
  supervisor(MyApp123.Module2, []),
  supervisor(MyApp123.Module3, []),

]

opts = [strategy: :one_for_one, name: MyApp123.Supervisor]
  Supervisor.start_link(children, opts)
end

What’s a way to add a children MyApp123.Module4 to that list and launch it dynamically, at runtime? Say, after I click a button on a page in my web app.

Check out DynamicSupervisor.

2 Likes

DynamicSupervisor example


Also Supervisor.Spec has been deprecated.

So supervisor(MyApp123.Module1, []) becomes

%{
  id: MyApp123.Module1,
  start: {MyApp123.Module1, :start_link, []},
  restart: :permanent,
  shutdown: :infinity,
  type: :supervisor,
  modules: [MyApp123.Module1]
}

However with a module based supervisor use Supervisor will include child_spec/1, so then it’s enough to specify:

children = [
  {MyApp123.Module1, []},
  {MyApp123.Module2, []},
  {MyApp123.Module3, []}
]

or even

children = [
  MyApp123.Module1,
  MyApp123.Module2,
  MyApp123.Module3
]
1 Like

Although you can also have MyApp123.Module1 define a child_spec/1 function returning that map. Then you can continue to have children = [MyApp123.Module1, ...] even if MyApp123.Module1 doesn’t use Supervisor.

To be noted also, use Supervisor isn’t the only one that defines a child_spec/1 in the module: GenServer for example does too. In other words, if MyApp123.Module1 uses GenServer you can also have children = [MyApp123.Module1, ...] without doing any extra work.

How to use it presicely for my case?

Convert the module you’re showing the code for in the first post to a dynamic module-based supervisor like https://hexdocs.pm/elixir/DynamicSupervisor.html#module-module-based-supervisors

Let’s say that module is called MyDynSup, and that you start it with the name: MyDynSup option. Then, you can Enum.each([MyApp123.Module1, MyApp123.Module2, MyApp123.Module3], fn mod -> DynamicSupervisor.start_child(MyDynSup, mod) end).

Later, to start a new child, simply call DynamicSupervisor.start_child(MyDynSup, MyApp123.Module3) (or DynamicSupervisor.start_child(MyDynSup, {MyApp123.Module3, []}) if you prefer).

Note that per the docs, the DynamicSupervisor starts without any children: they must be added after starting.

Not tested - something like:

def start(_type, _args) do
  children = [
   {DynamicSupervisor, strategy: :one_for_one, name: MyApp123.DynamicSupervisor}
  ]

  opts = [strategy: :one_for_one, name: MyApp123.Supervisor]
    Supervisor.start_link(children, opts)

  # MyApp123.ModuleX have to have a functional child_spec/1 to generate the detailed
  # child specification
  #
  DynamicSupervisor.start_child(MyApp123.DynamicSupervisor, MyApp123.Module1 )
  DynamicSupervisor.start_child(MyApp123.DynamicSupervisor, MyApp123.Module2 )
  DynamicSupervisor.start_child(MyApp123.DynamicSupervisor, MyApp123.Module3 )
  # ... or use Enum.each as above (or recursion ...) 
  # ... and personally I prefer a module based DynamicSupervisor rather than this ...
end

The generalized approach is to move the specifics of the child specification into the module implementing the process via module.child_spec(arg) rather than having to spell it out in detail in the children list.

Indeed, I’m just saying you typically don’t need to write the child spec map by hand:

defmodule MyServer do
  use GenServer

  ...
end

Supervisor.start_link([MyServer], strategy: :one_for_one)

Knowing the details is handy when you need to override:

use GenServer, restart: :transient, shutdown: 10_000

Yes, and also:

Supervisor.start_child(supervisor, Supervisor.child_spec(MyServer, id: :foo))
Supervisor.start_child(supervisor, Supervisor.child_spec(MyServer, id: :bar))