简体   繁体   中英

Understanding Elixir badarg error message

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

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 aka 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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM