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.Repo,
      ...
      {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}
  end

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.

2 Likes

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)
    end

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}}}
  end

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

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

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