Skip to content

DynamicSupervisor with extra_arguments crashes when restarting child #7369

@jvf

Description

@jvf

Environment

  • Elixir & Erlang versions (elixir --version): Elixir 1.6.1 (compiled with OTP 20)
  • Operating system: macOS 10.13.2

Current behavior

When using a DynamicSupervisor with :extra_arguments, a crash in one child kills all other children and restarts the supervisor.

Here is a slightly shortened example iex session to reproduce the behaviour, the corresponding code will be given below:

$ iex -S mix
iex(1)> Application.ensure_all_started(:sasl)
iex(2)> Demo.Supervisor.start_child(:foo, :bar, :baz)
iex(3)> Demo.Supervisor.start_child(:foo, :bar, :baz)
iex(4)> {:ok, pid} = Demo.Supervisor.start_child(:foo, :bar, :baz)
iex(5)> Supervisor.which_children(Demo.Application.Supervisor)
[{Demo.Supervisor, #PID<0.116.0>, :supervisor, [Demo.Supervisor]}]
iex(6)> Supervisor.which_children(Demo.Supervisor)
[
  {:undefined, #PID<0.128.0>, :worker, [Demo.Worker]},
  {:undefined, #PID<0.130.0>, :worker, [Demo.Worker]},
  {:undefined, #PID<0.132.0>, :worker, [Demo.Worker]}
]
iex(7)> Process.exit(pid, :kill)

=SUPERVISOR REPORT==== 19-Feb-2018::20:06:49 ===
     Supervisor: {local,'Elixir.Demo.Application.Supervisor'}
     Context:    child_terminated
     Reason:     shutdown
     Offender:   [{pid,<0.116.0>},
                  {id,'Elixir.Demo.Supervisor'},
                  {mfargs,
                      {'Elixir.Demo.Supervisor',start_link,[implicit_arg]}},
                  {restart_type,permanent},
                  {shutdown,infinity},
                  {child_type,supervisor}]
(...)
iex(8)> Supervisor.which_children(Demo.Application.Supervisor)
[{Demo.Supervisor, #PID<0.137.0>, :supervisor, [Demo.Supervisor]}]
iex(9)> Supervisor.which_children(Demo.Supervisor)
[]
iex(10)>

As you can see, killing one child crashes the Dynamic Supervisor and with it all of its children.

Expected behavior

The Supervisor should restart the child without crashing.

Example Code

The example code can also be found here as a ready-to-run example project. I tried to stick as close very closely to the example from the DynamicSupervisor documentation.

defmodule Demo.Application do
  use Application

  def start(_type, _args) do
    children = [{Demo.Supervisor, :implicit_arg}]
    opts = [strategy: :one_for_one, name: __MODULE__.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

defmodule Demo.Supervisor do
  use DynamicSupervisor

  def start_link(arg) do
    DynamicSupervisor.start_link(__MODULE__, arg, name: __MODULE__)
  end

  def start_child(foo, bar, baz) do
    # This will start child by calling Worker.start_link(implicit_arg, foo, bar, baz)
    spec = Supervisor.Spec.worker(Demo.Worker, [foo, bar, baz])
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

  def init(implicit_arg) do
    DynamicSupervisor.init(
      strategy: :one_for_one,
      extra_arguments: [implicit_arg]
    )
  end
end

defmodule Demo.Worker do
  use GenServer

  require Logger

  def start_link(:implicit_arg, :foo, :bar, :baz) do
    GenServer.start_link(__MODULE__, [:implicit_arg, :foo, :bar, :baz])
  end

  def init(:implicit_arg, :foo, :bar, :baz) do
    {:ok, []}
  end

  def handle_call(request, from, state) do
    Logger.debug(fn -> "#{request} from #{from}" end)
    {:reply, :ok, state}
  end
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions