How to Supervisor.start_child in a Supervision Tree?

I have an app like

stackdelivery
    ├── lib
            └── stackdelivery
                    └── application.ex
            └── stackgen
                     └── stackgen.ex
                     └── supervisor.ex
            stackdelivery.ex
    mix.exs

application.ex is

defmodule Stackdelivery.Application do
  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application
  use Supervisor

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

    # Define workers and child supervisors to be supervised
    children = [
      supervisor(Stackdelivery.StackGen.Supervisor, [])
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Stackdelivery.StackAppSupervisor]
    Supervisor.start_link(children, opts)
  end

end

supervisor.ex is

defmodule Stackdelivery.StackGen.Supervisor do
  @moduledoc """
  Supervisor for StackGen module
  """

  use Supervisor
  alias Stackdelivery.Stack

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

  def init(_) do
    IO.puts "starting StackGen.Supervisor supervisor.."
    children = [
      worker(Stack, [])
    ]
    opts = [strategy: :simple_one_for_one, name: Stackdelivery.StackGenSupervisor]
    supervise(children, opts)
  end

  def shoot, do: Supervisor.start_child(__MODULE__, [])
    
end

stackgen is a simple Stacn Genserver. for brevity:

defmodule Stackdelivery.Stack do
  use GenServer

  def init(_) do
    IO.puts "Starting a New GenServer Stack" 
    {:ok, []}
  end
....

I can’t start multiple child processes using Stackdelivery.StackGen.Supervisor.shoot

The way I am thinking is how supervision tree should be is:

Application (Main Supervisor)
└── StackGen.Supervisor (Module Supervisor)
         └── Stackgen (GenServer)

Now when Application.start_link happens, how can we invoke child proceses in StackGen.Supervisor for Stackgen workers?

Supervisor.start_child/2 needs a spec! See Supervisor.Spec

Also, I think it has to have an id if you’re starting multiple GenServers of the same module.

def shoot(unique_name) do
  spec = worker(Stackgen, [], [id: unique_name, restart: :transient])
  Supervisor.start_child(__MODULE__, spec)
end
4 Likes

isnt transient is default for simple_one_for_one?

Is this unique name unique everytime each pid starts?

You’re right. My code doesn’t use :simple_one_for_one. The app I took the example code from uses a :one_for_one. :cold_sweat:

I’ll play with your code and see if I can find out what’s going on.

should each time the id be unique?

In my app, I’m not using :simple_one_for_one strategy. Each GenServer I start has a unique id - that’s why the example I gave you has a unique_id argument.

can you give me an example with a code snippet?

in worker(module, args, options \\ [])

[id: module,
 function: :start_link,
 restart: :permanent,
 shutdown: 5000,
 modules: [module]]

id can be __MODULE__ right?

OK. I have a working example now. Sorry to have misled you with incorrect information. I hope this makes up for it :slight_smile:

Some notes:

  • I think having use Supervisor in application.ex ultimately caused the problem since they likely conflict with each other. I didn’t catch that at first.
  • I forgot about this gotcha: if running from IEx, the module name is prefixed with Elxir! (i.e. Elixir.StackDelivery.StackGen.Supervisor), so I’m not sure if that came into play or not

Nonetheless, I’ve tested this and it seems to be working fine.


application.ex

defmodule StackDelivery.Application do
  @moduledoc false

  use Application

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

    children = [
      supervisor(StackDelivery.StackGen.Supervisor, [])
    ]

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

stack_gen_supervisor.ex

defmodule StackDelivery.StackGen.Supervisor do
  use Supervisor
  require Logger
  alias StackDelivery.StackGen

  # this format is used at https://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html#simple-one-for-one-supervisors
  @name StackDelivery.StackGen.Supervisor

  def start_link do
    Supervisor.start_link __MODULE__, [], name: @name 
  end

  def shoot do
    Supervisor.start_child(@name, [])
  end

  def init(_) do
    Logger.info "starting StackDelivery.StackGenSupervisor"
    children = [
      worker(StackGen, [])
    ]
    opts = [strategy: :simple_one_for_one, name: StackDelivery.StackGenSupervisor]
    supervise(children, opts)
  end
end

stack_gen.ex

defmodule StackDelivery.StackGen do
  use GenServer
  require Logger

  def start_link(opts \\ []) do
    GenServer.start_link __MODULE__, opts
  end

  def init(_) do
    Logger.info "starting a new #{__MODULE__}"
    {:ok, []}
  end
end

mix.exs

defmodule StackDelivery.Mixfile do
  use Mix.Project

  def project do
    [app: :stack_delivery,
     version: "0.1.0",
     elixir: "~> 1.4",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  def application do
    [extra_applications: [:logger],
     mod: {StackDelivery.Application, []}]
  end

  defp deps do
    []
  end
end

idk, its not working for me

iex(1)> Stackdelivery.StackGen.Supervisor.start_link
starting..module is: Stackdelivery.StackGen.Supervisor
{:ok, #PID<0.116.0>}
iex(2)> Stackdelivery.StackGen.Supervisor.start_link

works when

defmodule Stackdelivery.StackGen.Supervisor do
  @moduledoc """
  Supervisor for StackGen module
  """

  use Supervisor
  alias Stackdelivery.Stack

  @name Stackdelivery.StackGen.Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [])
  end

wont work when Supervisor.start_link(__MODULE__, [], name: @name)