How to shutdown multiple processes correctly?

image

Above diagram describes the supervision tree for my simple Elixir app. I’m looking for some guidance :pray:

  1. the application is configured to start a DynamicSupervisor as its only child
  2. then in IEx I start the GenServer under the DynamicSupervisor.
  3. the GenServer starts a Task.Supervisor under the DynamicSupervisor and adds a bunch of Tasks to run under it.

Using Task.Supervisor.sync_nolink, the GenServer is able to detects when all those tasks are complete. When that happens handle_info will return {:stop, :normal, state} to terminate itself and the Task.Supervisor it stared but I’m noticing a couple of undesirable behaviours:

  1. it appears the GenServer is restarted and the whole thing is repeated 3 more times

  2. if multiple GenServers start and stop in quick successions the whole application auto shuts down without any errors like so:

     22:27:14.444 [notice] Application dyno exited: shutdown
    

I believe the GenServer and the Task.Supervisor should be started with restart: :temporary to avoid restarting and also because when processes terminate quickly in a short length of time the DynamicSupervisor keeps restarting and then eventually just gives up, leading to an application shut down :thinking:

The problem is I can’t figure out the correct child spec to pass to the DynamicSupervisor so it knows to not restart its children. Finally my next thought is maybe the GenServer doesn’t even need to be supervised :man_shrugging:

This is how the GenServer is started. I appreciate any help to understand what to do.

# Application
defmodule Dyno.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [{DynamicSupervisor, name: :ExecutorsSupervisor, type: :supervisor}]
    Supervisor.start_link(children, strategy: :one_for_one, name: Dyno.Supervisor)
  end
end
# GenServer
def run(workflow, supervisor \\ :ExecutorsSupervisor) do
  {:ok, steps_supervisor_pid} = DynamicSupervisor.start_child(supervisor, {Task.Supervisor, name: :"steps-supervisor:#{workflow.id}"})
  DynamicSupervisor.start_child(supervisor, {__MODULE__, {workflow, steps_supervisor_pid}})
end

def start_link({workflow, _}) do
  GenServer.start_link(__MODULE__, args, name: :"Workflow.Manager: #{workflow.id}")
end

Your GenServer should have restart: :transient so it will not be restarted if it terminates normally.

Thanks @lud

Is this what you mean because when I tried nothing changes?

def start_link({workflow, _}) do
  GenServer.start_link(__MODULE__, args, [
    name: :"Workflow.Manager: #{workflow.id}", restart: :transient
  ])
end

The :restart option is given in the child spec for the supervisor, not in the start arguments for GenServer. You can use use GenServer, restart: :transient for instance.

2 Likes