DynamicSupervisor question

I have a process which has two arguments given when started, a scope and a user.

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

Now I want to manage a lot of these processes via DynamicSupervisor:

def init(scope) do
  DynamicSupervisor.init(strategy: :one_for_one, extra_arguments: [scope])
end

def register(user) do
  spec = {MyProcess, [user]}

  DynamicSupervisor.start_child(__MODULE__, spec) do
    {:ok, pid} -> pid
    {:error, {:already_started, pid}} -> pid
  end
end

The idea behind this is to utilize DynamicSupervisor's extra_arguments option to pass the scope when starting the supervisor in my supervision tree.
However this does not work because only one process is ever started, all the other calls to DynamicSupervisor.start_child/2 return a {:error, {:already_started, pid}}!

I can get the code working like this:

def register(user) do
  # FIXME: Dirty dirty hack!
  %{extra_arguments: [scope]} = :sys.get_state(__MODULE__)
  name = {:via, Registry, {__MODULE__.Registry, [scope, user]}}

  spec = %{
    id: name,
    start: {MyProcess, :start_link, [user, [name: name]]}
  }

  case DynamicSupervisor.start_child(__MODULE__, spec) do
    {:ok, pid} -> pid
    {:error, {:already_started, pid}} -> pid
  end
end

However this feels very cumbersome and dirty because of

  • manually managing the id and name of my child processes
  • using :sys.get_state/1 to get :extra_arguments again
  • lots of boilerplate code:

In my opinion DynamicSupervisor should respect arguments given in the spec and in :extra_arguments to determine if a child process was already started or not.
Am I doing something wrong?

You should show your worker code too…

And I don’t do it like You do, I put spec inside the child, and the setting of the name via registry also in the child.

I don’t think %{id: name} set the name of the worker.

Before starting the child with the dynamic supervisor, You should be able to start it individually, in the console.