Socket Help - how do I get data coming back from a GenServer into my socket in another module?

Hey everyone,
Well I am swallowing whatever little pride I had left and I just need some help here. I considered myself doing pretty well in my Elixir progress but I really hit a wall here, and I imagine this is a relatively simple one. The simple issue is –

how in God’s name do I get data coming back from a GenServer into my socket in another module?

I am trying to just implement a simple timer and it is turning out to be one of the more difficult tasks I have had to do in any language, which makes me think I am just missing something really obvious here.

Then, on a very closely related note, does anyone have any good suggestions for reading up on ways to help debug an Phoenix/Elixir program, and in particular one dealing with Websockets. I consistently get them just hanging there with no explanation and its incredibly frustrating.

I will certainly post some snippets and things like that if its needed, but I just kinda figured this one might be an easy one for some more advanced folks. It is certainly driving me crazy though.

Thanks for all your help everyone, it is much appreciated

Lots of options! The best will depend a bit on your specific circumstances.

Is your GenServer a singleton process that is started when your application starts? Or is it something that is started by your socket? Or perhaps by some coordinating module such that it’s shared between a group of sockets?

Depending on the above, there are a lot of patterns for sharing information.
One that may be helpful is Phoenix.PubSub, which is probably already set up in your app. Your GenServer would broadcast messages on the relevant topic and your channel/socket would subscribe to that topic to receive those messages in its handle_info callback.

But that may be overkill – the best solution might just be Process.send(pid, :whatever_message). It’s hard to know without a bit more context and perhaps some code. :slightly_smiling_face:

You want to broadcast directly to clients connected to that socket ? you can look at Endpoint broadcast! - Phoenix.Channel — Phoenix v1.7.10

MyAppWeb.Endpoint.broadcast!("room:" <> rid, "new_msg", %{uid: uid, body: body})
MyAppWeb.Endpoint.broadcast!("room:superadmin", "new_msg", %{uid: uid, body: body})
  redirect(conn, to: "/")

Timers can be implemented in GenServer using Process.send_after

defmodule MyGenServer do
  use GenServer

  @impl true
  def init(_) do
    Process.send_after(self(), :tick, 1_000)
    {:ok, %{}}
  end

  @impl true
  def handle_info(:tick, state) do
    # Option 1 -  Do something here and schedule tick...
    Process.send_after(self(), :tick, 1_000)
    # Option 2 - Schedule tick first and do something here ...
    {:noreply, state}
  end
end

Little more details would help - where is it hanging in client side (eg. html page or only on mobile device? ) or server side ? When does this happen on connect or after connection ? Do all sockets hang or a particular socket ?

Thanks for the reply. So this is kinda where I keep banging my head against the wall. I’ve read all of these and followed the examples and just cannot seem to get anything to register or make that connection. My GenServer is a process that I start via a module by calling start_link(), and that GenServer does the countdown. I did make it to the PubSub direction too (out of desperation) and ran into similar issues, which always seems to be I cannot get handle_info callback to receive anything. I have a handle_info() in my module (LiveComponent) but am not even sure if that is a thing (is that only for GenServer). I’m pretty sure I just read a whole thing stating that it wasn’t, but not sure anymore :slight_smile:

I can seem to get process to process working, GenServer to process okay, but just the LiveComponent and the socket is escaping me.

defmodule SurfaceApp.Game.TimerServer do
  use GenServer
  require Kernel
  alias SurfaceAppWeb.Components.{McCard, Timer}
  alias Phoenix.PubSub
  # alias ExTyperacer.Logic.{Game, Player}
  @impl true
  def start_link() do
    GenServer.start_link(__MODULE__, %{})
  end

  @impl true
  def init(state) do
    IO.puts "Init timer"
    counter = 15
    uuid = "1111"
    timer = Process.send_after(self(), {:work, counter, uuid}, 1_000)
    {:ok, state}
  end
  @impl true
  def handle_call({:reset_timer, counter, uuid}, _from, %{timer: timer}) do
    :timer.cancel(timer)
    timer = Process.send_after(self(), {:work, counter, uuid}, 1_000)
    {:reply, :ok, %{timer: timer}}
  end
  @impl true
  def handle_info({:work, 0, uuid}, state) do
    IO.puts "Finished proccess"
    {:noreply, state}
  end
  @impl true
  def handle_info({:work, counter, uuid}, state) do
    IO.puts "Counting..."
    IO.inspect counter
    counter = counter - 1
    timer = Process.send_after(self(), {:work, counter, uuid},1_000)
    {:noreply, %{timer: timer}}
  end
  @impl true
  def handle_cast(:start_timer) do
    IO.puts "Hi"
  end
  @impl true
  def handle_info({:start_timer, counter, uuid}, state) do
    timer = Process.send_after(self(), {:work, counter, uuid}, 1_000)
    {:noreply, %{timer: timer}}
  end
    # So that unhanded messages don't error
  def handle_info(_, state) do
      {:ok, state}
  end
end

This is my GenServer. Pretty simple and similar to what is below. It works and I get the count but just need a way to send it back to my module. Here that is. It is really messy probably just because I’ve been throwing the kitchen sink at it, so I apologize for that. But again, thanks for your time and effort.

defmodule SurfaceAppWeb.Components.McCard do
    # This component will have some state. So use the stateful component.
    use Surface.LiveComponent
    alias Phoenix.PubSub
    alias SurfaceApp.Game
    alias SurfaceAppWeb.Components.{Button, Timer, GenTimer, Receiver}
    alias Surface.Components.Form.RadioButton
    alias SurfaceApp.Game.TimerServer

    data question_id, :string

    data socket_a, :string

    data seconds, :integer
    data submission_result, :string, default: ""
    data correct_answer, :string
    data category, :string
    data points_worth, :integer
    data question_text, :string
    data answer, :string
    data choice_one, :string
    data choice_two, :string
    data choice_three, :string
    data choice_four, :string
    data answered, :boolean, default: false

    data receiver_pid, :string
    data self_pid, :string
    # Use this to store our trivia card
    data trivia_card, :any

    # Can omit mount callback if using deault values in data assigns
    def mount(socket) do
      subscribe_timer()
      card = random_card()
      correct_answer = correct_answer(card)
      pid = self()
      Process.send_after(self(), :hi, 1_000)
      receiver_pid = spawn(Receiver, :awaiting_for_receive_messages, [])
      counter = 18
      uuid = "1111"
      timer = Process.send_after(self(), {:work, counter, uuid}, 1_000)
      IO.inspect Process.monitor(pid)
      IO.inspect Process.alive?(pid)
        {:ok,
          socket
          |> assign(seconds: 0)
          |> assign(socket_a: socket)
          |> assign(self_pid: pid)
          |> assign(receiver_pid: receiver_pid)
          |> assign(correct_answer: correct_answer)
          |> assign(question_id: card.id)
          |> assign(category: card.category)
          |> assign(points_worth: card.points_worth)
          |> assign(question_text: card.question_text)
          |> assign(answer: card.answer)
          |> assign(choice_one: card.choice_one)
          |> assign(choice_two: card.choice_two)
          |> assign(choice_three: card.choice_three)
          |> assign(choice_four: card.choice_four)
          |> assign(trivia_card: card)}
    end

    defp correct_answer(card) do
        cond do
          card.answer == card.choice_one -> "1"
          card.answer == card.choice_two -> "2"
          card.answer == card.choice_three -> "3"
          card.answer == card.choice_four -> "4"
        end
    end

    # defp subscribe_timer do
    #   IO.puts "subscribing to ..."
    #   PubSub.subscribe(SurfaceApp.PubSub, "timer_topic")
    # end

    # defp recv() do
    #   receive do
    #     :hi -> IO.puts "Chihi."
    #     :hi_from_timer -> IO.puts "Good Lord"
    #   end
    # end

    def handle_event("start_clock", _params, socket) do
      :timer.send_interval(1000, self(), :hi_from_timer)
      {:noreply, socket |> assign(:seconds, 15)}
    end

    def handle_info({:work, counter, uuid}, state) do
      IO.puts "F this"
      IO.inspect counter
    end

    def handle_info(:tick, %{assigns: %{seconds: seconds}} = socket) do
      IO.puts "handle info"
      {:noreply, socket |> assign(:seconds, seconds - 1)}
    end

    def handle_info({:seconds, count}, socket) do
      IO.puts "handle info"
      socket =
        socket
        |> assign(:seconds, 7)

      {:noreply, socket}
    end

    defp random_card do
        Game.list_mc_cards |> Enum.random()
    end

    defp add_points() do
        IO.puts "add+points"
    end

    def render(assigns) do
        ~F"""
        <div class="grid items-center justify-center">
          <div class="flex justify-center">
            <div class="block rounded-lg shadow-lg bg-white max-w-sm text-center">
              <h3>
                { @question_text }
              </h3>
            </div>
          </div>
          <Timer id="timer" />
                          { @seconds }
          <div class="grid grid-cols-2 gap-4">
            <div class="w-full p-2 lg:w-80">
                <div class="p-8 bg-white rounded shadow-md">
                  <Button label={ @choice_one } value="1" click={"answer"} />
                </div>
            </div>
            <div class="w-full p-2 lg:w-80">
                <div class="p-8 bg-white rounded shadow-md">
                  <Button label={ @choice_two } value="2" click={"answer"} />
                </div>
            </div>
            <div class="w-full p-2 lg:w-80">
                <div class="p-8 bg-white rounded shadow-md">
                  <Button label={ @choice_three } value="3" click={"answer"} />
                </div>
            </div>
            <div class="w-full p-2 lg:w-80">
                <div class="p-8 bg-white rounded shadow-md">
                  <Button label={ @choice_four } value="4" click={"answer"} />
                </div>
            </div>
          </div>
          <div :if={@answered} class="submiss-result">{ @submission_result }</div>
          <div :if={@answered} class="answer-reveal">Correct Answer: { @answer }</div>
          <button :on-click="answer">Submit</button>
          <button :if={@answered} :on-click="new">New Question</button>
          <Button label="Start Clock"  click={"start_clock"} />
          <button :on-click="start_clock">Start Clock</button>
        </div>
        """
    end

    # def update(assigns, socket) do
    #   IO.puts "update"
    #   {:ok, 
    #     socket
    #     |> assign(seconds: @seconds)}
    # end

    def handle_event("answer", value, socket) do
        choice = value["value"]
        # Process.send(socket.assigns.receiver_pid, "Hi", [])
        recv()
        card = socket.assigns.trivia_card
        correct_answer = socket.assigns.correct_answer
        if choice == correct_answer do add_points() else nil end
        result = if choice == correct_answer do "Correct" else "Incorrect" end
        {:noreply,
          socket
          |> assign(answer: card.answer)
          |> assign(submission_result: result)
          |> assign(answered: true)}
    end

    def handle_event("new", _value, socket) do
        card = random_card()
        correct_answer = correct_answer(card)
        {:noreply,
          socket
          |> assign(correct_answer: correct_answer)
          |> assign(category: card.category)
          |> assign(points_worth: card.points_worth)
          |> assign(question_text: card.question_text)
          |> assign(answer: "")
          |> assign(choice_one: card.choice_one)
          |> assign(choice_two: card.choice_two)
          |> assign(choice_three: card.choice_three)
          |> assign(choice_four: card.choice_four)
          |> assign(trivia_card: card)
          |> assign(answered: false)}
    end

    # def decrement(counter, state) do
    #     update(%{seconds: counter}, state)
    # end
end

Hey thanks for the reply. For the hanging it always seems to be a certain amount of time into working. I’ll start the server, it’ll work for four or five reloads and then just start hanging there. I do use Brave and know that it has some socket issue but I get same behavior in Edge and Chrome too. I get that blue bar which gets to like 80% and then just drags to 99% but never 100% (That is becoming my new screen of death) It is just on a normal HTML page.

I am not entirely confident it is not just my awful code doing it though too which is maybe why my question is rather generic. I’m kinda just looking for better ways to start trying to figure this stuff out myself, and quick google searches are not really landing me anywhere of value. Figured it was worth a shot.

Hi it seems you are trying to do this inside a component, but a component is not it’s own process, it lives inside a liveview process, so that will receive the messages, and it could receive them in a handle_info.