DynamicSupervisor: how to start child iff it doesn't exist?

James_E
Tue, 03 Dec 2024 17:53:23 +0000
Questions / Help

I’ve got this bit of code; I’m wondering why create_or_open_room always creates a new room, even when there is an existing child process with the same ID:

If a child specification with the specified ID already exists, child_spec is discarded and this function returns an error with :already_started or :already_present if the corresponding child process is running or not, respectively.
Supervisor.start_child/2 — Elixir v1.17.3

defmodule FooApp.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  require Logger

  def create_or_open_room(<<room_name::binary>>) do # FIXME this is supposed to be idempotent
    case DynamicSupervisor.start_child(
      FooApp.RoomSupervisor,
      %{id: room_name, start: {GenServer, :start_link, [FooApp.Application.Model, room_name]}}
    ) do
      {:ok, pid} -> Logger.debug("=====STARTED #{inspect pid}====="); pid
      :ignore -> raise "unreachable"
      {:error, {:already_started, pid}} -> Logger.debug("=====IDEMPOTENCE OK #{inspect pid}====="); pid
      {:error, error} -> raise inspect error
    end
  end

  @impl true
  def start(_type, _args) do
    children = [
      FooAppWeb.Telemetry,
      FooApp.Repo,
      {Ecto.Migrator,
        repos: Application.fetch_env!(:fooApp, :ecto_repos),
        skip: skip_migrations?()},
      # {DNSCluster, query: Application.get_env(:fooApp, :dns_cluster_query) || :ignore},
      # {Phoenix.PubSub, name: FooApp.PubSub}, # for general app communication
      {Phoenix.PubSub, name: FooApp.RoomPubSub}, # ONLY for room statechange announcements, to avoid collision between UGC room names and an actual system topic
      # Start the Finch HTTP client for sending emails
      # {Finch, name: FooApp.Finch},
      # Start a worker by calling: FooApp.Worker.start_link(arg)
      # {FooApp.Worker, arg},
      {DynamicSupervisor, name: FooApp.RoomSupervisor},
      # Start to serve requests, typically the last entry
      FooAppWeb.Endpoint
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: FooApp.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  @impl true
  def config_change(changed, _new, removed) do
    FooAppWeb.Endpoint.config_change(changed, removed)
    :ok
  end

  defp skip_migrations?() do
    # By default, sqlite migrations are run when using a release
    System.get_env("RELEASE_NAME") != nil
  end
end

FooApp.Application.Model (a boring GenServer, not relevant, including for completeness) (click for more details)
10 posts - 4 participants