Starting a few workers for the same module at the start of application

Hello everyone,

I’m trying to run an application with a bunch of workers which are using the module, but can’t figure out what I’m doing wrong here. The code is pretty straightforward

defmodule Matchmaking.Application do
  @moduledoc false
  use Application
  import Supervisor.Spec, warn: false

  @config Confex.fetch_env!(:matchmaking, __MODULE__)

  defp spawn_workers(module, opts, concurrency) when is_integer(concurrency) and concurrency >= 1 do
    for id <- 1..concurrency do 
      worker(module, opts, id: String.to_atom("#{module}_#{id}"), restart: :transient)
    end
  end

  def start(_type, _args) do    
    children = List.flatten([
      supervisor(Matchmaking.AMQP.Connection, []),
      spawn_workers(Matchmaking.Middleware.Worker, [], 2),
    ])

    opts = [strategy: :one_for_one, name: Matchmaking.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Which is leading to the next error:

** (Mix) Could not start application matchmaking: Matchmaking.Application.start(:normal, []) returned an error: shutdown: failed to start child: Matchmaking.Middleware.Worker_2
    ** (EXIT) already started: #PID<0.239.0>

Can anybody help me how to figure out with the following issue? The amount of workers will be always fixed at the each start and won’t be changed later.

Are you doing this:

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

or something similar in the Worker module?

If so, you need to remove the name option:

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok)
  end
1 Like

Other than the fact that you shouldn’t be using Supervisor.Spec anymore this stripped down version works as expected - so the error doesn’t seem to originate from the code shown.

defmodule Matchmaking.Application do
  use Application
  import Supervisor.Spec, warn: false

  defp spawn_workers(module, args, concurrency)
      when is_integer(concurrency) and concurrency >= 1 do
    for id <- 1..concurrency do
      worker(
        module,
        args,
        [id: String.to_atom("#{module}_#{id}"), restart: :transient]
      )
    end
  end

  def start(_type, _args) do
    # List all child processes to be supervised
    children = List.flatten([
      spawn_workers(Worker,[],2)
    ])

    opts = [strategy: :one_for_one, name: Matchmaking.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
defmodule Worker do
  use GenServer

  def start_link() do
    GenServer.start_link(__MODULE__, []);
  end

  def init(_args) do
    ref = Process.send_after(self(), :init, 2000)
    {:ok, ref}
  end

  def handle_info(:init, state),
    do: {:stop, :normal, state}

  def terminate(reason, _state),
    do: IO.puts("terminate: #{inspect self()} #{inspect reason}")

end
$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Compiling 2 files (.ex)
Generated matchmaking app

Interactive Elixir (1.6.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
terminate: #PID<0.121.0> :normal
terminate: #PID<0.122.0> :normal

Without Supervisor.spec:

defmodule Matchmaking.Application do
  use Application

  defp worker_specs(module, concurrency)
      when is_integer(concurrency) and concurrency >= 1,
    do: Enum.map(1..concurrency, &(worker_spec(module, &1)))

  defp worker_spec(module, id),
    do: %{
      id: String.to_atom("#{module}_#{id}"),
      start: {module, :start_link, []},
      type: :worker,
      restart: :transient
    }

  def start(_type, _args) do
    # i.e. no need to flatten list - use list cons instead
    children = [{Matchmaking.OtherSupervisor, []} | worker_specs(Worker,2)]

    opts = [strategy: :one_for_one, name: Matchmaking.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
defmodule Matchmaking.OtherSupervisor do
  # automatically defines child_spec/1
  use Supervisor

  def start_link(arg) do
    Supervisor.start_link(__MODULE__, arg, name: __MODULE__);
  end

  def init(_arg) do
    children = []

    Supervisor.init(children, strategy: :one_for_one)
  end
end
1 Like

Yes, the :name option in my worker module was the issue here. Thank you so much!

1 Like