How to detect if a user left a Phoenix channel due to a network disconnect?

I have an Elixir/Phoenix server app and the clients connect through the build in channels system via websockets. Now I want to detect when an user leaves a channel.

Sidenote: I'm using the javascript client library inside a Google Chrome Extension. For this I extracted the ES6 code from Phoenix, transpiled it to javascript, and tweaked it a little so it runs standalone.

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

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

Why and how do I fix this?

I played around with the timeoutoption of transport :websocket, Phoenix.Transports.WebSocket but this did not work out.

Update: With the new awesome Phoenix 1.2 Presence stuff, this should not be needed anymore.


The proper way to do this is to not trap exits in your channel, and instead have another process monitor you. When you go down, it can invoke 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}

def leave(room_id, user_id) do
  # handle user leaving

# lib/my_app/channel_watcher.ex

defmodule ChannelWatcher do
  use GenServer

  ## Client API

  def monitor(server_name, pid, mfa) do, {:monitor, pid, mfa})

  def demonitor(server_name, pid) do, {:demonitor, pid})

  ## Server API

  def start_link(name) do
    GenServer.start_link(__MODULE__, [], name: name)

  def init(_) do
    Process.flag(:trap_exit, true)
    {:ok, %{channels:}}

  def handle_call({:monitor, pid, mfa}, _from, state) do
    {:reply, :ok, put_channel(state, pid, mfa)}

  def handle_call({:demonitor, pid}, _from, state) do
    case HashDict.fetch(state.channels, pid) do
      :error       -> {:reply, :ok, state}
      {:ok,  _mfa} ->
        {:reply, :ok, drop_channel(state, pid)}

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

  defp drop_channel(state, pid) do
    %{state | channels: HashDict.delete(state.channels, pid)}

  defp put_channel(state, pid, mfa) do
    %{state | channels: HashDict.put(state.channels, pid, mfa)}

Need Your Help

@Documented annotation in java

java eclipse annotations

What's the purpose of @Documented annotation in java?

Working with SAML 2.0 in C# .NET 4.5

c# .net saml saml-2.0

I am trying to use pure .NET (no external classes, controls, helpers) to create a SAML message. I found some code on the interwebs; this is what I have: