How to determine if a user has left the Phoenix channel due to a network outage?

I have an Elixir / Phoenix server application, and clients connect through the build system in channels through websockets. Now I want to determine when the user leaves the channel.

Sidenote: I am using the javascript client library inside the Google Chrome extension. To do this, I extracted ES6 code from Phoenix, converted it to javascript, and modified it a bit to make it work autonomously.

Now, when I just close the popup, the server immediately starts the terminate/2 function with reason = {:shutdown, :closed} . There is no close callback on the extension side, so this is great!

But when the client simply loses the network connection (I connected the second computer and just pulled out the network plug), then terminate/2 will not start.

Why and how to fix it?

I played with the timeout transport :websocket, Phoenix.Transports.WebSocket option transport :websocket, Phoenix.Transports.WebSocket , but this did not work.

Update: With the exciting new Phoenix 1.2 Presence material, this is no longer necessary.

+12
elixir websocket phoenix-framework channels
source share
1 answer

The right way to do this is not to intercept the exits in your channel, but instead have another process that follows you. When you go down, it can trigger a callback. Below is a snippet to get you started:

 # lib/my_app.ex children = [ ... worker(ChannelWatcher, [:rooms]) ] # web/channels/room_channel.ex def join("rooms:", <> id, params, socket) do uid = socket.assigns.user_id] :ok = ChannelWatcher.monitor(:rooms, self(), {__MODULE__, :leave, [id, uid]}) {:ok, socket} end def leave(room_id, user_id) do # handle user leaving end # lib/my_app/channel_watcher.ex defmodule ChannelWatcher do use GenServer ## Client API def monitor(server_name, pid, mfa) do GenServer.call(server_name, {:monitor, pid, mfa}) end def demonitor(server_name, pid) do GenServer.call(server_name, {:demonitor, pid}) end ## Server API def start_link(name) do GenServer.start_link(__MODULE__, [], name: name) end def init(_) do Process.flag(:trap_exit, true) {:ok, %{channels: HashDict.new()}} end def handle_call({:monitor, pid, mfa}, _from, state) do Process.link(pid) {:reply, :ok, put_channel(state, pid, mfa)} end def handle_call({:demonitor, pid}, _from, state) do case HashDict.fetch(state.channels, pid) do :error -> {:reply, :ok, state} {:ok, _mfa} -> Process.unlink(pid) {:reply, :ok, drop_channel(state, pid)} end end def handle_info({:EXIT, pid, _reason}, state) do case HashDict.fetch(state.channels, pid) do :error -> {:noreply, state} {:ok, {mod, func, args}} -> Task.start_link(fn -> apply(mod, func, args) end) {:noreply, drop_channel(state, pid)} end end defp drop_channel(state, pid) do %{state | channels: HashDict.delete(state.channels, pid)} end defp put_channel(state, pid, mfa) do %{state | channels: HashDict.put(state.channels, pid, mfa)} end end 
+27
source share

All Articles