3
votes

When trying to start my process from a DynamicSupervisor im getting the following error:

{:error,
 {:EXIT,
  {:badarg,
   [
     {:erlang, :apply,
      [
        BfgEngine.MarketService,
        :start_link,
        {{BfgEngine.MarketService, :start_link, ["1111"]}, :permanent, 5000,
         :worker, [BfgEngine.MarketService]}
      ], []},
     {: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]}
   ]}}}

The code im using is:

  def start_market(market_id) do
    spec = {MarketService, market_id}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

However it is not clear to me what is going wrong. What argument to which function is it thats not correct? How do I break down and read the given error message?

Update:

This is the init method of my supervisor:

  @impl true
  def init(initial_arg) do
    DynamicSupervisor.init(
      strategy: :one_for_one,
      extra_arguments: [initial_arg]
    )
  end

Update 2: This is the start_link of market_service:

  def start_link(market_id) when is_bitstring(market_id) do
    GenServer.start_link(__MODULE__, market_id, name: via_tuple(market_id))
  end

Im using the default child_spec im getting from GenServer

Update 3: Changing to:

  def start_market(market_id) do
    spec = {MarketService, market_id: market_id}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

Gives:

{:error,
 {:undef,
  [
    {BfgEngine.MarketService, :start_link, [[], [market_id: "222"]], []},
    {DynamicSupervisor, :start_child, 3,
     [file: 'lib/dynamic_supervisor.ex', line: 654]},
    {DynamicSupervisor, :handle_start_child, 2,
     [file: 'lib/dynamic_supervisor.ex', line: 640]},
    {: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]}
  ]}}

Changing to:

  def start_market(market_id) do
    spec = {MarketService, :market_id, market_id}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

Gives:

** (ArgumentError) supervisors expect each child to be one of:

  * a module
  * a {module, arg} tuple
  * a child specification as a map with at least the :id and :start fields
  * or a tuple with 6 elements generated by Supervisor.Spec (deprecated)

Got: {BfgEngine.MarketService, :market_id, "222"}

    (elixir) lib/supervisor.ex:657: Supervisor.init_child/1
    (elixir) lib/supervisor.ex:744: Supervisor.child_spec/2
    (elixir) lib/dynamic_supervisor.ex:304: DynamicSupervisor.start_child/2
3
Could you share the definition of your Supervisor's init function as well? - Paweł Dawczak
The problem is in the start_link code of your MarketService. The things to look at there are your start_link function as well as your child_spec. Can you add those to your post? - AnilRedshift

3 Answers

1
votes

You got badarg exception to function erlang:apply/3 when there are three arguments BfgEngine.MarketService, :start_link and {{BfgEngine.MarketService, :start_link, ["1111"]}, :permanent, 5000, :worker, [BfgEngine.MarketService]} and it happen in function supervisor:do_start_child_i/3.

The arguments to the function erlang:apply/3 should be MFA a.k.a Module, Function, Arguments. {{BfgEngine.MarketService, :start_link, ["1111"]}, :permanent, 5000, :worker, [BfgEngine.MarketService]} is not Arguments because it obviously is not a list of arguments. From your code, I can guess the error is the content of variable spec. You should provide some proplist or map. I don't know, you should read the documentation of DynamicSupervisor more carefully.

1
votes

The error message

As of understanding the error message thrown by Elixir, you can refer to the official Erlang documentation. The section about errors and exceptions from Learn You Some Erlang For Great Good can help. The answer of @Hynek -Pichi Vychodil is also accurate.

Your specific problem

As @Milan Jaric mentioned, your error would come from:

  def start_market(market_id) do
    spec = {MarketService, market_id}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

But not only! DynamicSupervisor.start_child(__MODULE__, spec) is calling MarketService.start_link/1! Your problem lies in the combination of this function from the DynamicSupervisor module, and the way you parse values in MarketService.start_link/1:

  def start_link(market_id) when is_bitstring(market_id) do
    GenServer.start_link(__MODULE__, market_id, name: via_tuple(market_id))
  end

Indeed, this code should work, if you also have implemented MarketService.init/1 correctly. I am not able to reproduce the error. Are you sure market_id is really a bitstring?

Personally, I had based my code on the official documentation:

defmodule MySupervisor do
  use DynamicSupervisor

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

  def start_child(foo, bar, baz) do
    # If MyWorker is not using the new child specs, we need to pass a map:
    # spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
    spec = {MyWorker, foo: foo, bar: bar, baz: baz}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

  @impl true
  def init(init_arg) do
    DynamicSupervisor.init(
      strategy: :one_for_one,
      extra_arguments: [init_arg]
    )
  end
end

As you can see, they suggest to use a Keyword list here:

spec = {MyWorker, foo: foo, bar: bar, baz: baz}

It works only if you've implemented MyWorker.start_link/1 as follows:

def start_link(args) do
   foo = Keyword.fetch!(args, :foo)
   bar = Keyword.fetch!(args, :bar)
   baz = Keyword.fetch!(args, :baz)
   Genserver.start_link(__MODULE__, {foo, bar, baz}, [])

def init({foo, bar, baz}) do
   # do something...
   state = {foo, bar, baz}
   {:ok, state}

In your case, if you change start_market/1 to:

  def start_market(market_id) do
      spec = {MarketService, market_id: market_id}
      DynamicSupervisor.start_child(__MODULE__, spec)
  end

It will not work because this MarketService.start_link/1 will fail:

 def start_link(market_id) when is_bitstring(market_id) do
    GenServer.start_link(__MODULE__, market_id, name: via_tuple(market_id))
  end

market_id here is not a bitstring but a Keyword list. So you got to modify MarketService.start_link/1 function to:

  def start_link(args) when is_list(args) do
    market_id = Keyword.fetch!(args, :market_id)
    GenServer.start_link(__MODULE__, market_id, name: via_tuple(market_id))
  end

And write a MarketService.init/1 as follows:

  def init(market_id) do
     # do something... Let's keep it simple for the example:
     state = market_id
     {:ok, state}
  end

Helpful resources

0
votes

It is hard to tell from posted code, but could you try changing start_market to:

  def start_market(market_id) do
    spec = {MarketService, :market_id, market_id}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

UPDATE (Below are two options):

  def start_market(market_id) do
    spec = &{
      id: MarketService,
      start: {MarketService, start_link, [market_id]},
      type: :worker
    }
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

OR

  def start_market(market_id) do
    spec = {MarketService, [market_id]}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end