当网络连接突然断开时,channel 中的 terminate 函数是不会执行的. 为了捕获到客户端的网络连接中断,我们需要不捕获 channel 的退出(因为它不会退出),而是需要由另一个进程来监控.
# 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,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,mfa},_from,state) do
Process.link(pid)
{:reply,:ok,put_channel(state,mfa)
end
def handle_call(:demonitor,pid},state) do
case HashDict.fetch(state.channels,pid) do
:error -> {:reply,state}
{:ok,_mfa} ->
Process.unlink(pid)
{:reply,drop_channel(state,pid)}
end
end
def handle_info({:EXIT,_reason},pid) do
:error -> {:noreply,{mod,func,args}} ->
Task.start_link(fn -> apply(mod,args) end)
{:noreply,pid)}
end
end
defp drop_channel(state,pid) do
%{state | channels: HashDict.delete(state.channels,pid)}}
end
defp put_channel(state,mfa) do
%{state | channels: HashDict.put(channels,mfa)}}
end
end