Why is the output different for these child specifications?

Hi,

I’ve been learning Elixir and used this very good article by Jose: http://howistart.org/posts/elixir/1/index.html

I’m trying to understand what makes a difference with child specification. The official documentation is different from the article however the article’s version is the only one that works.

On supervisors it specifies children this way:

children = [
  worker(Portal.Door, [])
]

Output is good:
{:ok, #PID<0.142.0>}

However trying any other specification from the official documentation:

%{
  id: Portal.Door,
  start: {Portal.Door, :start_link, [[]]}
}

children = [
  Portal.Door
]

throws error:

{:error,
  {:EXIT,
    {:undef,
      [
        {Portal.Door, :start_link, [[], :orange], []},
        {:supervisor, :do_start_child_i, 3, [file: 'supervisor.erl', line: 379]},
        {:supervisor, :handle_call, 3, [file: 'supervisor.erl', line: 404]},
        {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 661]},
        {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 690]},
        {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}
       ]}}}

Based on the documentation I’d like to understand what is happening, why one works and how other specification types could be made to work as well.

Thanks for any answer! Cheers!

Can you post the complete Portal.Door.child_spec/1 code?

It should look something like this

def child_spec(_args) do
  %{
    id: __MODULE__,
    start: {__MODULE__, :start_link, []}, // note the single brackets
    restart: :permanent,
    shutdown: 5000,
    type: :worker
   }
end

The example shown here https://hexdocs.pm/elixir/Supervisor.html#module-child_spec-1 passes a list [:hello] as first argument, not a single argument :hello. This can be a little confusing.

Hi hlx!

Thank you for replying.

There is no child_spec/1 in the Portal.Door module. Yet it still works when I’m following the instructions directly. This is exactly what I’m trying to understand

Here’s my Door code:

defmodule Portal.Door do
  use Agent

  def start_link(color) do
    Agent.start_link(fn -> [] end, name: color)
  end

  def get(door) do
    Agent.get(door, fn list -> list end)
  end

  def push(door, value) do
    Agent.update(door, fn list -> [value|list] end)
  end

  def pop(door) do
    Agent.get_and_update(door, fn
      [] -> {:error, []}
      [h|t] -> {{:ok, h}, t}
    end)
  end
end

Yet it still works if the child is specified as instructed here
http://howistart.org/posts/elixir/1/

application.ex

defmodule Portal.Application do
  @moduledoc false
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false
    children = [
      worker(Portal.Door, [])
      # Portal.Door
    ]

    opts = [strategy: :simple_one_for_one, name: Portal.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

On the other hand:

children = [
  Portal.Door
]

or

children = [
  {Portal.Door, []}
]

or

%{
  id: Portal.Door,
  start: {Portal.Door, :start_link, [[]]}
}

(all examples in the official documentation on Supervisors) do not work with the example setup from the article (where no default value is passed to start_link as those are passed at runtime – I’d like to emphasize again that the worker() function does work though as expected). Which is confusing for me because it says that the default type is worker for each child unless something else is specified:

_ :type - specifies that the child process is a :worker or a :supervisor . This key is optional and defaults to :worker ._

Let me know if what I’m writing is not clear. I’m confused and feel contradicted myself by all this.

Trying to simplify what I would like to achieve with this question.

I’m hoping to understand the difference between child specification syntax:

children = [
worker(Portal.Door, [])
]

VS

children = [
Portal.Door
]

How come the first one works while the second results in an exception?

It is the second one that is coming from the official documentation of all places! Yet it throws an error while based on what I have read this effectively should be the same as the first one – it should default to a worker type and have an empty arg list added too. And I can see from the exception that my empty list is nested in another arg list when I’m trying to run the module.

Which all in all makes me believe it is just me missing a crucial piece of information here.

I hope rephrasing my issue like this makes the question more understandable and easier to answer correctly.

Many thanks! Cheers!

In your start_link/1 function you declare an argument called color which you use to set the name.

When doing worker(Portal.Door, []) the name of the worker is set to nil

If you want to do the same without the worker you need to specify a name as first argument, like so:


children = [
  {Portal.Door, nil}
]

But I’m guessing a color name would be better, {Portal.Door, :orange}

You can see what worker outputs by running this in your console:

iex(1)> Supervisor.Spec.worker(Portal.Door, [:orange])

edit: the name part was wrong, it should be nil

This is deprecated as of Elixir 1.6 and shouldn’t be used anymore. This macro expands to a map, specifiying how the worker should be started. So the main configuration is done in the Supervisor.

This specifies, that we want to retrieve the child_spec from the module Portal.Door at runtime, by calling its child_spec/1 function (implicitely created when doing use GenServer). This function will return the map as it would have been created by the old macro. This way, default configuration can be done in the child_spec/1 of the workers implementation module. By wrapping the module name in a tuple one can alter the result of the function by adding “arguments”.

Thanks for the detailed response! It’s great to receive such thoughtful and well-meaning answers.

So, could you confirm that based on your answer the proper way to handle supervising children as of elixir 1.7.3 should be something like:

door.ex:

defmodule Portal.Door do
use Agent

def child_spec(_args) do
%{
id: MODULE,
start: {MODULE, :start_link, }, #note the single brackets
restart: :permanent,
shutdown: 5000,
type: :worker
}
end

(…)

application.ex

defmodule Portal.Application do
@moduledoc false
use Application

def start(_type, _args) do
# import Supervisor.Spec, warn: false
children = [
# worker(Portal.Door, )
Portal.Door
]

opts = [strategy: :simple_one_for_one, name: Portal.Supervisor]
Supervisor.start_link(children, opts)

end
end

I can confirm that this works just as the old version did and no error is thrown.

Two questions:

  • I’m using use Agent here just to confirm: that doesn’t implicitly create a child_spec/1 like GenServer would? (Doesn’t seem like based on my quick test with commenting out the child_spec/1 method)
  • the :worker here (and by default) is the same as the macro was in the old setup it is just cast as a key now in the new way of setting things up, is that correct? (checked https://hexdocs.pm/elixir/Supervisor.Spec.html vs https://hexdocs.pm/elixir/Supervisor.html)

Thanks for your thorough response as well. The explanation tilted me into the right direction in understanding how the worker macro was working and why the mix of the old Supervisor.Spec didn’t mesh well with the setup in the 1.7.3 documentation.

I’m now wondering if this otherwise pretty great and fun quickstart article could be updated to reflect the current architecture of Elixir Supervisors.

It should:

Finally note use Agent defines a child_spec/1 function, allowing the defined module to be put under a supervision tree.


Also I have a small nitpick because of nomenclature, it’s a function, not a method, as a method is bound to objects.

To nitpick: :slight_smile: I don’t think that’s nitpick. Things have names and we should adhere to correct usage so there is no misunderstanding. Thanks for catching that!

Yes, I read that too but whenever I comment out the child_spec/1 and try to run the application an :error is returned on start_link/1

door.ex

defmodule Portal.Door do
use Agent

# def child_spec(_args) do
# %{
# id: __MODULE__,
# start: {__MODULE__, :start_link, []}, #note the single brackets
# restart: :permanent,
# shutdown: 5000,
# type: :worker
# }
# end

def start_link(color) do
Agent.start_link(fn → end, name: color)
end

results in:

{:error,
{:EXIT,
{:undef,
[
{Portal.Door, :start_link, [, :blue], },
{:supervisor, :do_start_child_i, 3, [file: ‘supervisor.erl’, line: 379]},
{:supervisor, :handle_call, 3, [file: ‘supervisor.erl’, line: 404]},
{:gen_server, :try_handle_call, 4, [file: ‘gen_server.erl’, line: 661]},
{:gen_server, :handle_msg, 6, [file: ‘gen_server.erl’, line: 690]},
{:proc_lib, :init_p_do_apply, 3, [file: ‘proc_lib.erl’, line: 249]}
]}}}

That’s because the default implementation doesn’t ignore it’s argument, but tries to make sense from it and uses it to call your start_link.

I’m actually wondering where :blue comes from…

PS: please also note that :simple_one_for_one is deprecated as well and nowadays a DynamicSupervisor is the tool to choose instead.