DynamicSupervisor Disallows Creating Two Children From Different Modules with the Same ID

I want to create a child for every time a game is created by my users:

      {:ok, room} = Sem.RoomMessaging.get_room_code()
      IO.inspect(Supervisor.child_spec({Sem.Room, %{room: room}}, id: room, shutdown: 10_000))
      {:ok, pid} =
        DynamicSupervisor.start_child(
          Sem.RoomManager,
          Supervisor.child_spec({Sem.Room, %{room: room}}, id: room, shutdown: 10_000)
        )

when I console log the output of child_spec I get a child spec with a unique id

%{
  id: :"767476717986",
  shutdown: 10000,
  start: {Sem.Room, :start_link, [%{room: :"767476717986"}]}
}

however when I call this a second time I get:

** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.372.0>}}

Any idea on how to fix this?

Hello and welcome,

Don’t use the room as id for users, use something unique…

2 Likes

get_room_code()/1 gets a completely unique and unused room code
I log them every time I create a new child:

iex(1)> %{
  id: :"718687887279",
  shutdown: 10000,
  start: {Sem.Room, :start_link, [%{room: :"718687887279"}]}
}
iex(1)> %{
  id: :"767476717986",
  shutdown: 10000,
  start: {Sem.Room, :start_link, [%{room: :"767476717986"}]}
}
iex(1)>
11:31:02.192 [error] Ranch protocol #PID<0.375.0> of listener None.HTTP (connection #PID<0.374.0>, stream id 1) terminated
an exception was raised:
    ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.372.0>}}
        (sem 0.1.0) lib/client.ex:18: Sem.SocketHandler.init/2
        (cowboy 2.9.0) c:/Users/ebarr/Desktop/coding/stacked/kertamen/sem/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.9.0) c:/Users/ebarr/Desktop/coding/stacked/kertamen/sem/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
        (cowboy 2.9.0) c:/Users/ebarr/Desktop/coding/stacked/kertamen/sem/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
        (stdlib 3.15) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

here is get_room_code()/1

  @spec get_room_code :: {:ok, atom()}
  def get_room_code() do
    rand = randomcode(6)
    if (Registry.lookup(Sem.RoomRegistry, rand) != []) do
      {:ok, get_room_code()}
    else
      {:ok, String.to_atom(rand)}
    end
  end

How do You name the worker?

It should be the name param in the start_link of the worker.

1 Like

The problem was I was using the module as the name of the Genserver (the worker) instance

@spec start_link(props()) :: :ignore | {:error, any} | {:ok, pid}

  def start_link(props) do

    GenServer.start_link(__MODULE__, props, name: __MODULE__)

  end

I replaced it with the unique atom and it worked

It’s common to use a Registry with dynamic supervisor, and use via in the worker.

It could be local, or distributed.

Also, I prefer to use something like this.

case DynamicSupervisor.start_child(
          Sem.RoomManager,
          Supervisor.child_spec({Sem.Room, %{room: room}}, id: room, shutdown: 10_000)
        ) do
  {:ok, pid} -> pid
  {:error, {:already_started, pid}} -> pid
  _ -> # something else...
end
1 Like

Also, the title should be

DynamicSupervisor Disallows Creating Two Children From The Same Module with Different IDs