Supervisor.start_child problem

591 views
Skip to first unread message

Josef Vanek

unread,
Sep 16, 2015, 8:18:45 AM9/16/15
to elixir-lang-talk
Hi all,

I am complete beginner to Elixir and I'm trying to create a very simple Supervisor/GenServer app in which
i'd like to have several different GenServers, but not lauinched all at start time by the supervise(children, :strategy ...) call..

In clear, let's say we have one supervisor, and possibly 3 different (different init params, different functionalities) GenServers.
How could I achieve my goal to start at the first init only GenServer1,
and then only, during app execution, ask to add GenServer2 and still later GenServer3 please ?

I looked through all books and documentations I've found, but this topic doesn't seem to be treated anywhere...

For example if I try:

My.Supervisor
       use Supervisor

def init(arg \\ []) do
children = [
worker(MyGenServer1, [], [])
]
supervise(children, [{:strategy, :one_for_one}, {:max_restarts, 1}, {:max_seconds, 5}])
end

      ....
end


then later, how could I invoke 

Supervisor.start_child(My.Supervisor, [MyGenServer1])

and later

Supervisor.start_child(My.Supervisor, [MyGenServer2, param1, param2])

and finally

Supervisor.start_child(My.Supervisor, [MyGenServer3, param1])

this doesn't work, I receive ** (EXIT) time out when executing start_child function...




Any help will be very appreciated, thanks in advance.

Josef

José Valim

unread,
Sep 16, 2015, 8:21:15 AM9/16/15
to elixir-l...@googlegroups.com
You call Supervisor.start_child, passing the name or PID of the supervisor and the child spec:

# Start supervisor with no children
iex(1)> {:ok, pid} = Supervisor.start_link [], strategy: :one_for_one
{:ok, #PID<0.85.0>}

# Import the worker spec definitions
iex(2)> import Supervisor.Spec
nil

# Start a task in the supervisor. notice restart is temporary
# because the task only prints something and exits.
iex(3)> Supervisor.start_child pid, worker(Task, [fn -> IO.puts "hello" end], restart: :temporary)
hello
{:ok, #PID<0.88.0>}



José Valim
Skype: jv.ptec
Founder and Director of R&D

On Wed, Sep 16, 2015 at 2:13 PM, Josef Vanek <vanek...@gmail.com> wrote:
Hi all,

I am complete beginner to Elixir and I'm trying to create a very simple Supervisor/GenServer app in which
i'd like to have several different GenServers, but not lauinched all at start time by the supervise(children, :strategy ...) call..

In clear, let's say we have one supervisor, and possibly 3 different (different init params, different functionalities) GenServers.
How could I achieve my goal to start at the first init only GenServer1,
and then only, during app execution, ask to add GenServer2 and still later GenServer3 please ?

I looked through all books and documentations I've found, but this topic doesn't seem to be treated anywhere...

For example:

In my 

My.Supervisor

def init(arg \\ []) do
Logger.log(:debug, "Supervisor: launching children...")
children = [
worker(CardRegistry, [{:supervisor, self}], []),
# worker(BaseCard, [{:name, "Base Card"}], []),
worker(SampleCard, [{:name, "Sample Card"}], [])
]
supervise(children, [{:strategy, :one_for_one}, {:max_restarts, 1}, {:max_seconds, 5}])
end

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/9cb4de87-5269-4933-9583-9d92e0b05832%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Josef Vanek

unread,
Sep 16, 2015, 9:52:07 AM9/16/15
to elixir-l...@googlegroups.com
Hi José,

Many thanks for so quick reply. I've immediately tested your example, and even if it worksperfectly in iex,
I still have the timeout issue in my code.

As I didn't want to post all my code I send only excerpts here :

First the supervisor:

defmodule FooSupervisor do
use Supervisor

@moduledoc """
"""

def start_link do
Logger.log(:info, "Supervisor: starting...")
Supervisor.start_link(__MODULE__, [], [{:name, __MODULE__}])
end

def init(arg \\ []) do
Logger.log(:debug, "Supervisor: launching children...")
children = [
worker(FooRegistry, [], []),
worker(SampleFoo, [], [])
]
supervise(children, [{:strategy, :one_for_one}, {:max_restarts, 1}, {:max_seconds, 5}])
end

end

then the FooRegistry :

defmodule FooRegistryConfig do
defstruct uuid: String.to_atom("#{__MODULE__}.#{UUID.uuid1(:hex)}")
end


defmodule FooRegistry do
    use GenServer

    def start_link do
        Logger.log(:info, "Starting FooRegistry...")
        GenServer.start_link(__MODULE__, [], [{:name, __MODULE__}])
    end

    def init(_opts \\ []) do
        Logger.log(:debug, "#{__MODULE__} initializing")
        config = %FooRegistryConfig{}
        {:ok, config}
    end

    def handle_call({command, msg}, _from, config) do
        Logger.log(:debug, "Received #{msg}")
        case command do
            # Foo with pid <_from> needs Foo in <msg>
            :needs ->
                needs_foo(_from, msg)
        end
        {:reply, :ok, config}
    end

    defp needs_foo({foo_pid, _}, needed_foo) do
        Logger.log(:debug, "Foo needs #{needed_foo}")
        import Supervisor.Spec
        Supervisor.start_child(FooSupervisor, worker(needed_foo, [], [restart: :permanent]))
    end

end

then BaseFoo :

defmodule BaseFoo do
use GenServer

def start_link do
Logger.log(:info, "Starting BaseFoo...")
GenServer.start_link(__MODULE__, [], [{:name, __MODULE__}])
end

def init(_opts \\ []) do
Logger.log(:debug, "#{__MODULE__} initializing")
        config = %BaseFooConfig{}
{:ok, config}
end

    ...
end

and finally the SampleFoo:

defmodule SampleFooConfig do
defstruct uuid: String.to_atom("#{__MODULE__}.#{UUID.uuid1(:hex)}")
end


defmodule SampleFoo do
  use GenServer

  @moduledoc false

def start_link do
Logger.log(:info, "Starting SampleFoo...")
GenServer.start_link(__MODULE__, [], [{:name, __MODULE__}])
end

def init(_opts \\ []) do
Logger.log(:debug, "#{__MODULE__} initializing")
        config = %SampleFooConfig{}
GenServer.call(FooRegistry, {:needs, BaseFoo}, 500)
{:ok, config}
end

def handle_call(_msg, _from, config) do
Logger.log(:debug, "SampleFoo: Received #{_msg}")
{:reply, :ok, config}
end

....
end

So if I launch this code, the output says:

2015-09-16 14:59:51.349 [info] Starting FooTest...
2015-09-16 14:59:51.349 [info] Supervisor: starting...
2015-09-16 14:59:51.349 [debug] Supervisor: launching children...
2015-09-16 14:59:51.349 [info] Starting FooRegistry...
2015-09-16 14:59:51.349 [debug] Elixir.FooRegistry initializing
2015-09-16 14:59:51.349 [info] Starting SampleFoo...
2015-09-16 14:59:51.349 [debug] Elixir.SampleFoo initializing
2015-09-16 14:59:51.349 [debug] Received Elixir.BaseFoo
2015-09-16 14:59:51.349 [debug] Foo needs Elixir.BaseFoo

=INFO REPORT==== 16-Sep-2015::14:59:51 ===
    application: logger
    exited: stopped
    type: temporary
** (Mix) Could not start application foo_controller: FooTest.start(:normal, []) returned an error: shutdown: failed to start child: SampleFoo
    ** (EXIT) exited in: GenServer.call(FooRegistry, {:needs, BaseFoo}, 500)
        ** (EXIT) time out


The timeout occurs on the Supervisor.start_child line and I cannot figure why...
Would you be so kind to help me with this please ?

Many thanks,
Josef


worker(FooRegistry, [{:supervisor, self}], []),
# worker(BaseFoo, [{:name, "Base Foo"}], []),
worker(SampleFoo, [{:name, "Sample Foo"}], [])
]
supervise(children, [{:strategy, :one_for_one}, {:max_restarts, 1}, {:max_seconds, 5}])
end

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/9cb4de87-5269-4933-9583-9d92e0b05832%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.

José Valim

unread,
Sep 16, 2015, 10:11:51 AM9/16/15
to elixir-l...@googlegroups.com
I believe you have a deadlock.

The supervisor will block until the init function of the child being started returns. Here is what is happening:

1. You are starting SampleCard (supervisor starts to wait)
2. In SampleCard init, you ask the registry for BaseCard
3. The registry asks the supervisor to start the child
4. The supervisor is still waiting for SampleCard to return from init so it can't start the child in time

You can make the SampleCard initialization async. In init, send a message to yourself:

    send self(), :start_base_card

Then handle this message in handle_info where you will effectively do the connection:

    def handle_info(:start_base_card, config) do
      GenServer.call(CardRegistry, {:needs, BaseCard}, 500)
      {:noreply, config}
    end




José Valim
Skype: jv.ptec
Founder and Director of R&D

Josef Vanek

unread,
Sep 16, 2015, 10:52:53 AM9/16/15
to elixir-l...@googlegroups.com
Fantastic ! Many many thanks, it works nicely.
The idea of sending messages to self is great.

Josef

Reply all
Reply to author
Forward
0 new messages