Phoenix websocket - not receiving channel events sent while disconnected to channel; after connecting to channel?

I am using Phoenix channels as backend on a VPS and a react native android client as front end.

  • As long as I am connected to the channel, things run great. No complaints.
  • My issue is when I am not connected to the channel, like when my front end is minimized or killed. The flow is as follows:
  1. User launches a timer when the client is in foreground. I use channel.push to push an even to the server
    channel.push('pomodoro1)
    On the server I handle it by using push()
def handle_in("pomodoro1", _payload, socket) do
    push(socket, "status", %{status: "running"})

    :timer.sleep(5000)
    push(socket, "status", %{status: "finished"})
    send_notification()
    {:reply, :ok, socket}
  end

as you can see I wait for 5 mins between the two “pushes”

  1. Client -> I use channel.on to do necessary updates on client
    channel.on("status", resp => {setPomodoroStatus(resp["status"])})
    Now user kills the app, so the client as expected disconnects from the server channel. Now push() sent by the server is ofc not registered by the client. Now, the user launches the app after many hours. Now the app connects to the server channel but the last push() sent out by the server is not received by the client because when it was pushed, the client was disconnected. So, how can I resolve this issue?

When a client disconnects the channel gets closed as well, so you loose the channel state. See https://hexdocs.pm/phoenix/Phoenix.Channel.html#module-terminate . If you want to keep the state you’ll need to “save” it in an other process where you control the lifecycle like a genserver. 2 caveats if you do it like this. The state of the genserver is still not persisted, if the process is restarted, the state is lost. Second, if you have a lots of people using your service you could end up with a big amount of genservers for clients that are not connected.

So depending on your needs you could also make it really persistent and save the state to a database. When a client connects you then read the state from the DB and you save the state as needed on changes.

@theagilecoder Tangentially, this is not a great way to do a timer in a GenServer. calling :timer.sleep will block the genserver preventing it from pushing other updates. You probably want to use Process.send_after(self(), :done, 5000), possibly combined with a {:noreply tuple to wait on sending the reply.

2 Likes

Thanks for the response. I think you are right; I will have to persist it in DB. chapter 10 of programming phoenix book also takes the same approach.

Thanks. I should have done some research. :timer was the first thing I came across online and started using it.

Edit- Looking at the docs, I also saw cancel_timer(timer_ref, options \\ []) Cancels a timer returned by send_after/3.. So, I think now I can cancel my timers too :slight_smile: