tags:

views:

204

answers:

3

From what I've read in the docs, gen_servers don't trap exits. Moreover, my understanding is that if a process starts another process with spawn_link, and the child process crashes, the parent crashes too.

However, this is not what I'm seeing. I've got a gen_server that spawn_links a process. I set up a function in the child process like so:

test(JobIsHalfDone) -> 
    case JobIsHalfDone of
        true -> exit(test);
        false -> ok
    end.

When this function sends the exit signal, I get a message:

** exception exit: test

Yet its parent gen_server keeps right on ticking. Why?

+3  A: 

Lets compare experience, I have the following behaviour that says that it does die when a linked process dies.

1> {ok, Pid} = gen_server:start(server, [], []).
{ok,<0.33.0>}
2> gen_server:call(Pid, nice_to_see_you).
thanks
3> gen_server:call(Pid, nice_to_see_you).
thanks
4> gen_server:call(Pid, twap).           
oh_noes
5> gen_server:call(Pid, nice_to_see_you).
** exception exit: {noproc,{gen_server,call,[<0.33.0>,nice_to_see_you]}}
     in function  gen_server:call/2

That is, I made it crash by spawn_link:ing a process that does not much but dying, bringing the server down with it.

-module(server).
-compile(export_all).
init(_) ->
  {ok, []}.
handle_call(twap, _From, State) ->
   spawn_link(fun suicidal/0),
   {reply, oh_noes, State};
handle_call(_, _From, State) ->
   {reply, thanks, State}.
suicidal() ->
   exit(kaboom).

The fact that you see the "*** exception exit: test" seems to indicate that you used gen_server:start_link/3 from the shell, so that you gen server is linked to the shell process. That might introduce additional confusion, but nothing explaining why you would think the server didnt die.

Christian
Yep, you're right. I just verified it with a minimal gen_server myself. Something else must be going on in my real gen_server.
mwt
Figured it out. I had placed the spawn_link loose in the gen_server module instead of including it as a callback. Apparently that breaks the gen_server abstraction.
mwt
So what does it take for you to close an answer as final?
Christian
I don't think I have enough reputation for that.
mwt
A: 

I was wrong, which I guess leaves me a step closer to understanding why it's not killing my real server.

-module(crash).
-behaviour(gen_server).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
     terminate/2, code_change/3]).

-export([crash/0]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, []}.

crash() ->
    gen_server:cast(?MODULE, crash).

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

handle_cast(crash, State) ->
    spawn_link(fun() ->
                   crash_loop(0)
           end),
    {noreply, State};
handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

crash_loop(Counter) ->
    case Counter =:= 10 of
    true ->
        exit(crash_loop);
    false ->
        ok
    end,
    timer:sleep(100),
    crash_loop(Counter + 1).

...and testing in shell:

11> c(crash).
{ok,crash}
12> crash:start_link().
{ok,<0.67.0>}
13> crash:crash().
ok
14> ** exception error: crash_loop
14> whereis(crash).
undefined
mwt
A: 

I think the context of your spawn_linked process is the process that made the call, not the gen_server. Either spawn the loop in the init() callback or do a gen_server:call() and spawn the loop in a handle_call. Then the loop will be linked to the process that is running the gen_server.