Running GenServer only once

I created a GenServer to keep track of the current index in case the client reconnects to the socket. I intend to only use one GenServer.

I can make the GenServer start automatically by adding it to the children list of supervisor but my GenServer state is depended on the result of a query so I cannot put it there.

 children = [
      # Start the Ecto repository
      {Senti.QuestionState, [%{}]}

I started the GenServer in my join callback. I’m aware that the start_link will always be called when there are new clients connected but I have no idea where should I put this one.

I have my code below to describe more about my issue.

  def join("session:" <> session_id, _payload, socket) do
    presentation_state = Questionnaire.get_presentation_info(session_id)
    {:ok, _pid} = QuestionState.start_link(question_state(presentation_state))

    question_index = QuestionState.get_next_question_index() || 0

    {:ok, %{presentation: presentation_state, current_index: question_index}, socket}

If information provided above is insufficient, I will update it ASAP.
Thank you.

Application.start_phase/3 seems to be a good fit. Add a DynamicSupervisor to the main supervision tree and make it start and supervise your GenServer in the start_phase.


I am not sure you need start_phase at all. You can query your repo in the init of your GenServer and that will be executed before the supervisor starts the next child.

So you can start your repo, start the GenServer that depends on your repo, and then start the rest of the app (web stack with channels).

1 Like

This is what I do after I realized I can do something like this. I’m using Registry for my GenServer and in join callback of my channel I added this

case QuestionState.lookup(session_id) do
      {:ok, _pid} -> IO.puts("GenServer already exist for #{session_id}. Skipping...")
      {:error, :not_found} -> {:ok, _pid} = SessionSupervisor.start(presentation_state, session_id)

Is that okay?

I don’t know because I don’t know why you need a session genserver for each channel process.

But I guess it could be more direct to handle the :error in lookup/1 directly.

defmodule QuestionState do
  def via(session_id) do
    {:via, Registry, {MyRegistry, {:session, session_id}}}

  def start_link(session_id) do
    GenServer.start_link(__MODULE__, init_arg, name: via(session_id))

  def start_supervised(session_id) do
    SessionSupervisor.start(presentation_state, session_id)

  def lookup(session_id) do
    case start_supervised(session_id) do
      {:ok, pid} -> {:ok, pid}
      {:error, {:already_started, pid}} -> {:ok, pid}