I am trying to set up a supervision tree for a scheduler application (note using Elixir 1.5 syntax). The application should work so that:
- Application boots with a registry & the scheduler supervisor
- The SchedulerSupervisor boots, and allows children to be dynamically added via the
start_child
callback. This takes the initialisation args that are used to build the schedule state. - The Scheduler, on initialisation, registers its name to the Registry, and initialises with a struct that holds the state (Scheduler then has functions from manipulating schedules, but that isn't relevant to the issue I'm having).
If I don't pass any arguments, I can get this working - the Schedules created are not registered, and I have to modify the state after creation. As soon as I try to add arguments, the system errors out - I know it's just a syntax misunderstanding on my part, but I cannot for the life of me figure out what I'm doing wrong. I haven't found the docs terribly helpful here, and I've tried copying and modifying examples from GH, GH Gists and from articles online, but I cannot get it to work.
Current setup - ideally I would want to pass id
, period
and targets
as arguments to start_child
, but can't even get it working with a single argument, so just sticking with the one until I can get it running:
The Application:
defmodule Assay.Application do
use Application
def start(_type, _args) do
children = [
{Assay.SchedulerSupervisor, []},
{Registry, keys: :unique, name: Assay.Scheduler.Registry}
]
opts = [strategy: :one_for_all, name: Assay.Supervisor]
Supervisor.start_link(children, opts)
end
end
The Supervisor:
defmodule Assay.SchedulerSupervisor do
use Supervisor
@name Assay.SchedulerSupervisor
def start_link(_args) do
Supervisor.start_link(__MODULE__, :ok, name: @name)
end
def start_schedule(id) do
Supervisor.start_child(@name, [id])
end
def init(_) do
Supervisor.init([Assay.Scheduler], [strategy: :simple_one_for_one, name: @name])
end
end
The GenServer (only relevant initialisation functions shown)
defmodule Assay.Scheduler do
use GenServer
alias Assay.Scheduler
require Logger
defstruct targets: [], period: 60_000, id: nil, active: false
def start_link(id) do
GenServer.start_link(__MODULE__, [id], [name: via_tuple(id)])
end
def init([id]) do
Logger.info "starting a new #{__MODULE__} with id #{id}"
{:ok, %Scheduler{id: id}}
end
end
edit: actual error might help - I can see that the args are wrong, I just can't figure out why:
{:error,
{:EXIT,
{:undef,
[{Assay.Scheduler, :start_link, [[], 1], []},
{:supervisor, :do_start_child_i, 3, [file: 'supervisor.erl', line: 381]},
{:supervisor, :handle_call, 3, [file: 'supervisor.erl', line: 406]},
{:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 636]},
{:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 665]},
{:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}}}
For
:simple_one_for_one
supervisors,Supervisor.start_child
calls the start function with the arguments given to it with the arguments specified in the Child Specification. When usingSupervisor.init
, the child specification is taken from the module'schild_spec/1
function in Elixir 1.5. Since you're usingGenServer
and not specifying a custom start function and[]
is passed tochild_spec/1
, this defaults to[[]]
which means your function ends up being called with two arguments,[]
and1
if theid
is1
and you get an undefined function error.You can fix this by explicitly saying you don't want the GenServer to provide any arguments to the start function in
child_spec
by changingto
Now the function will be called correctly, with only one argument, which will be the
id
.will print: