Channel messages being delivered more than once

Hi!

I’m just beginning to get to grips with Phoenix, in particular the Channels feature. I am finding that a broadcast message gets delivered more than once. Is this to be expected? I think there must be a bug lurking in my code somewhere but I’m having trouble finding it, if anyone can shed any light I’d be most grateful! Thank you.

Here’s the channel code:

defmodule MyAppWeb.RoomChannel do
  require Logger

  use Phoenix.Channel
  alias MyApp.RoomPresence

  def join("room:" <> _roomId, _message, socket) do
    send(self(), :after_join)
    # Send back the id that we have assigned to this particular user
    {:ok, %{ id: socket.assigns.user_id }, socket}
  end

  def handle_info(:after_join, socket) do
    # Record the user's presence
    RoomPresence.track_user_join(socket, socket.assigns.user_id)
    # Push the presence state (i.e. a list of all current users) to the newly joined socket
    push socket, "presence_state", RoomPresence.list(socket)

    {:noreply, socket}
  end

  def handle_in("broadcast", %{"body" => body}, socket) do
    broadcast_from!(socket, "broadcast", %{body: body})
    {:noreply, socket}
  end
end

On the JS side I have this:

channel.push("broadcast", {
  body: { test: "payload" },
});

Slight complication is that I’m using React on the client side (code not included here for brevity). One of my theories was that re-renders on the client side were triggering multiple connections. I believe I have ruled this out.

So I guess my questions are:

  • Do phoenix channels make any guarantees about the number of times a given message is delivered?
  • Is there a means of testing this somehow?
  • Any tips for debugging channels generally are very welcome!

Ah rats, I’ve found the problem which existed entirely in my client code. In case it’s helpful, here’s what I did to work out what was happening:

First I attached an incrementing id field to each websocket message. Next I added an intercept to my Channel like so:

intercept ["broadcast"]

  def handle_out("broadcast", payload, socket) do
    IO.inspect(payload)
    push(socket, "broadcast", payload)
    {:noreply, socket}
  end

In this way I could see that Phoenix was only sending the message once.

I took a look at my client code again and checked out client side re-rendering. It turns out the problem was that I was calling channel.on() every time the React component was being re-rendered. My fix was to clean up properly by calling channel.off("broadcast", subscriberRef).

Problem solved… thanks for reading, hopefully this might help someone else. Aside question: would it be appropriate for socket.on() to log a warning (or even throw an error) if you add duplicate subscribers? Perhaps this could be a dev only feature to help catch mistakes (like mine).

2 Likes