Autostarting the workers

Hi :wave:

I’m looking for a best way to autostart a worker process.

I have my main module autogenerated as a part of supervised app: MyApp.Application registered as MyApp.Supervisor and then I’m adding a DynamicSupervisor as it’s child:

defmodule MyApp.Application do
  @moduledoc false

  use Application

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

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

I also have a box standard worker that requires a single string arg:

defmodule MyApp.Worker do
  use GenServer

  def start_link(symbol) do
    GenServer.start_link(__MODULE__, symbol, name: :"#{__MODULE__}-#{symbol}")
  end

  def init(symbol) do
    Logger.info("Starting worker #{symbol}")
    {:ok, symbol}
  end
end

Question is - let’s say that I have a table in database that lists all symbols that I need to autostart - I need to start many worker processes on start of the application. Currently I added another module/process that is a child of the MyApp.Application that on init -> handle_continue queries the database and starts workers:

  def start_worker(symbol) do
    DynamicSupervisor.start_child(
      MyApp.DynamicSupervisor,
      {MyApp.Worker, symbol}
    )
  end

Is this the only/best way to do it? How would you approach this problem?

Thanks in advance

You could use Task to reduce some of the boilerplate:

  def start(_type, _args) do
    children = [
      {
        DynamicSupervisor,
        strategy: :one_for_one,
        name: MyApp.DynamicSupervisor
      },
      {Task, &load_workers/0}
    ]

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

  defp load_workers() do
    # start children
  end
1 Like

Thank you for the reply, it looks nice but thinking about it now… how will restarting work in case of crash of the DynamicSupervisor? Obviously new DynamicSupervisor will be started but then I need my task to be run once again. I could use one_for_all strategy in MyApp.Application as long as it will be happy with Task exiting after a second or so.

Second thought - this load_workers function will be using Ecto to query and have a little bit of logic - I was thinking that the “best practice” is to avoid putting logic into supervisors etc?

The supervisor strategy here would be :rest_for_all. If you do not want to have all your app with this strategy, then you need one more layer of supervisors : App -> SymbolsSupervisorTop -> [SymbolsDynamicSupervisor, Task].

So you can set the :rest_for_one strategy on SymbolsSupervisorTop only.

Second thought - this load_workers function will be using Ecto to query and have a little bit of logic - I was thinking that the “best practice” is to avoid putting logic into supervisors etc?

The logic will not be executed by the supervisor process but by the Task, so it is all that matters. Also this code will be strongly related to starting/restarting processes so why not put it in the dynamic supervisor module.

2 Likes

I think this is probably the best option - I wasn’t aware that DynamicSupervisor is also a behaviour! I will add starting children logic there and querying to the Task probably - I need to see how this will look like.

1 Like

And remember those are just functions so you can still move them to their own file at any time.

1 Like