<div dir="ltr"><div><div><div><div><div><div><div><div>Hi,<br><br></div>I'm using a cowboy websocket handler as a simple chat transport.<br></div>The websocket handler pass messages to a session gen_server.<br></div><div>
The session gen_server traps exits and links to the websocket pid <br></div>so if the websocket pid dies, the session gen_server should terminate as well.<br><br></div></div>I sometimes see sessions that thinks that the websocket pid is still alive although checking with is_process_alive/1 shows it is not. That leads to zombie sessions that accumulate over time.<br>
<br></div>Is there a race condition in my code or something else I'm missing in catching the termination of the websocket pid?<br><br></div>Please see a simplified version of the websocket handler and the session gen_server below.<br>
<br></div><div>Thanks<br></div><div><br>-module(my_session).<br>-behaviour(gen_server).<br><br>-export([handle_message/2]).<br>-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).<br>
<br>-record(state, {ws}).<br><br>handle_message(undefined, Msg) -><br>    case supervisor:start_child(my_session_sup, [Msg, self()]) of<br>        {error, Reason} -> {close, 1000, <<"">>};<br>        {ok, Pid} -><br>
</div><div>            gen_server:call(Pid, {websocket, self()}),<br></div><div>            self() ! {session, Pid},<br>            {text, <<"ok">>}<br>    end;<br>handle_message(Pid, Msg) -><br>    gen_server:call(Pid, {msg, Msg}).<br>
<br>init([Msg, WS]) -><br>    case auth(Msg) of<br>        false -> {stop, normal};<br>        true -><br>            process_flag(trap_exit,true),<br>            {ok, #state{})<br>    end.<br><br>handle_call({websocket, Pid}, _, State) -><br>
</div><div>  link(Pid),<br></div>  {reply, ok, State#state{ws=Pid}};<br><div>handle_call({msg, Msg}, _, State) -><br>    Resp = msg(Msg, State),<br>   {reply, Resp, State}.<br><br>handle_info({'EXIT', _Pid, _Reason}, State) -><br>
    {stop, normal, State}.<br><br>...<br><br><br>-module(ws_handler).<br>-behaviour(cowboy_websocket_handler).<br><br>-export([init/3]).<br>-export([websocket_init/3]).<br>-export([websocket_handle/3]).<br>-export([websocket_info/3]).<br>
-export([websocket_terminate/3]).<br><br>-record(state, {tref, session}).<br><br>init({tcp, http}, _Req, _Opts) -><br>    {upgrade, protocol, cowboy_websocket}.<br><br>websocket_init(_TransportName, Req, _Opts) -><br>
    Tref = erlang:send_after(10000, self(), timeout),<br>    {ok, Req, #state{tref=Tref}}.<br><br>websocket_handle({text, ""}, Req, State) -><br>    erlang:cancel_timer(State#state.tref),<br>    Tref = erlang:send_after(10000, self(), timeout),<br>
    {ok, Req, State#state{tref=Tref}};    <br>websocket_handle({text, Msg}, Req, State) -><br>    Tref = erlang:send_after(10000, self(), timeout),<br>    Resp = my_session:handle_message(State#state.session, Msg),<br>
    {reply, Resp, Req, State#state{tref=Tref}};<br>websocket_handle(_Data, Req, State) -><br>    {ok, Req, State}.<br><br>websocket_info({session, Pid}, Req, State) -><br>    {ok, Req, State#state{session=Pid}};<br>
websocket_info({terminate}, Req, State) -><br>    {reply, {close, 1000, <<"">>}, Req, State};    <br>websocket_info({timeout, _Ref, Msg}, Req, State) -><br>    {shutdown, Req, State};<br>websocket_info(_Info, Req, State) -><br>
    {ok, Req, State}.<br><br>websocket_terminate(_Reason, _Req, _State) -><br>    ok.<br></div></div>