I interpreted his advice to go with the fully featured map-based version of the spec:
%{
id: Pipeline,
start: {Pipeline, :start_link, [job_module, [name: ...]]
}
rather than the tuple short form, not necessarily modifying start_link
.
GenServer (which it is) start_link/2
FYI: actually it’s start_link/3
start_link(module, args, options \\ [])
Ultimately it’s this line
start: {Pipeline, :start_link, [job_module, [name: ...]]
that determines how the child process is started. Pipeline
is simply the module that owns the startin function, :start_link
identifies the function to “start” the process (it could be set :fred
- and then you better provide a fred
function) and the list that follows are the arguments to be used when calling start_link
(or fred
).
So there really is no default signature - the tuple spec assumes a name of start_link
but that is a mere convention but there is no fixed arity.
Ultimately the function is run with something like Kernel.apply/3
- so the number of elements in the args
list ties into the function’s arity - keeping in mind that the last element can be keyword list to mimic variadic arguments (I personally find it confusing when the enclosing brackets are left out “for convenience”).
Just to crudely demonstrate:
defmodule Pipeline do
use Supervisor
def init(args) do
{:ok, args}
end
defp queue_name(job_module),
do: String.to_atom("#{job_module}-queue")
def spec_map(job_module),
do: %{
id: Pipeline,
start: {
Pipeline,
:fake_start_link,
[job_module, [name: queue_name(job_module)]]
}
}
def fake_start_link(job_module, opts) do
IO.puts("job_module: #{inspect job_module}");
IO.puts("opts: #{inspect opts}")
// GenServer.start_link(job_module, [], opts)
end
end
job_module = Queue;
spec = Supervisor.child_spec(Pipeline, Pipeline.spec_map(job_module))
IO.inspect spec
{m,f,a} = spec.start
Kernel.apply(m,f,a)
$ elixir demo.exs
%{
id: Pipeline,
start: {Pipeline, :fake_start_link, [Queue, [name: :"Elixir.Queue-queue"]]},
type: :supervisor
}
job_module: Queue
opts: [name: :"Elixir.Queue-queue"]