Search code examples
erlangerlang-otperlang-shellgen-server

Error in gen_server also terminates the calling process?


My gen_server contains a method like this:

handle_call(error, From, State) ->
    io:format("Inside the handle_call error~n"),
    1/0.

It provides a start (not start_link) function:

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

normal_call() ->
    gen_server:call(?MODULE, {normal}).

error_call() ->
    gen_server:call(?MODULE, error).

I call the start function from the shell:

c(some_module).
some_module:start().

I then call the error call that terminates the server process because of the divide-by-zero error, but it also terminates the shell (the calling process). I don't understand why? They are not linked but still the shell restarts with a new pid. Is that an expected behavior of gen_server, or am I doing something wrong?

Updated : It is still not working,, to help i m posting complete code.

-module(some_test).
-behaviour(gen_server).
-compile(export_all).

%% api functions, can be directly used by the user

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

normal_call() ->
    gen_server:call(?MODULE, normal, infinity).

error_call() ->
    gen_server:call(?MODULE, error, infinity).

%% service specific function shoule not be call directly

init(_) ->
    io:format("Inside the init ( ~p )~n", [self()]),
    io:format("Inside the init...~n"),
    {ok, nil}.

handle_call(normal, _, _) ->
    io:format("Inside the handle_call normal~n"),
    {reply, ok, nil};

handle_call(error, _, nil) ->
    io:format("Inside the handle_call error~n"),
    1/0.

terminate(Reason, _) ->
    io:format("Inside the terminate~p~n", [Reason]).

%% just to complete

handle_info(_, _) ->
    {noreply, nil}.

handle_cast(_, _) ->
    {noreply, nil}.

code_change(_, _, _) ->
    {ok, nil}.

%% a single test function that prove that called process was removed immeditely, did not wait for 5 seconds

test_function() ->
    io:format("Id is : ~p~n", [self()]),
    ?MODULE:start(),
    ?MODULE:normal_call(),
    ?MODULE:error_call(),
    io:format("Id is : ~p~n", [self()]).   %% never reached :(

and to start i used this :-

c(some_test).
some_test:test_function().

and got the output :-

20> some_test:test_function().
Id is : <0.84.0>
Inside the init ( <0.92.0> )
Inside the init...
Inside the handle_call normal
Inside the handle_call error
Inside the terminate{badarith,
                        [{some_test,handle_call,3,
                             [{file,"some_test.erl"},{line,29}]},
                         {gen_server,try_handle_call,4,
                             [{file,"gen_server.erl"},{line,629}]},
                         {gen_server,handle_msg,5,
                             [{file,"gen_server.erl"},{line,661}]},
                         {proc_lib,init_p_do_apply,3,
                             [{file,"proc_lib.erl"},{line,240}]}]}

=ERROR REPORT==== 3-Dec-2015::18:36:22 ===
** Generic server some_test terminating
** Last message in was error
** When Server state == nil
** Reason for termination ==
** {badarith,[{some_test,handle_call,3,[{file,"some_test.erl"},{line,29
              {gen_server,try_handle_call,4,
                          [{file,"gen_server.erl"},{line,629}]},
              {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,6
              {proc_lib,init_p_do_apply,3,
                        [{file,"proc_lib.erl"},{line,240}]}]}
** exception exit: {{badarith,
                        [{some_test,handle_call,3,
                             [{file,"some_test.erl"},{line,29}]},
                         {gen_server,try_handle_call,4,
                             [{file,"gen_server.erl"},{line,629}]},
                         {gen_server,handle_msg,5,
                             [{file,"gen_server.erl"},{line,661}]},
                         {proc_lib,init_p_do_apply,3,
                             [{file,"proc_lib.erl"},{line,240}]}]},
                    {gen_server,call,[some_test,error,infinity]}}
     in function  gen_server:call/3 (gen_server.erl, line 212)
     in call from some_test:test_function/0 (some_test.erl, line 51)
21>

so as we can see that last line after the some_test:error_call() is never called ?? because calling process also terminated ??


Solution

  • gen_server:call/2,3 is a synchronous messaging function. That means it does not return until the gen_server sends a reply to the sender. In this case the gen_server is crashing, and so never sends a response. The result of this is that the calling function times out, and the result of that timeout is a crash.

    You will notice that the callee crashes instantly, but the caller crashes 5 seconds later. That is because the default timeout is 5000 milliseconds (check the docs, linked above). Try setting it to infinity -- your calling process will just hang, blocking forever waiting for a response that is never coming.

    [UPDATE: The call coming directly from the shell is crashing immediately because an exception is being raised in the middle of its execution. This is different from the timeout expiring -- both cases result in crashes, but an exception is instant.]

    The way around this is to send asynchronous messages, using gen_server:cast/2. Try defining this:

    handle_cast(error, State) ->
        io:format("Inside the handle_cast error~n"),
        1/0.
    

    This will only cause the callee to crash; the caller will continue on the instant the message is away. Its like throwing a baseball and walking away instead of throwing a boomerang and waiting.

    As you gain experience with Erlang you will tend to write code with as many casts as possible and only resort to calls when the situation really demands a synchronous message (some state value actually depends on the sequence of events). This makes a system much more loosely coupled in many ways, and makes your calling functions resistant to failures in the processes they are sending data to.

    EDIT

    The shell crashing is because the call it was making crashed during execution. This does not happen with an asynch message, though. I added another module and broadened yours to illustrate the point. Now there is a new module called some_tester that relays everything we send, so it crashes, not the shell. Here are the relevant bits:

    -module(some_tester).
    -compile(export_all).
    
    start() ->
        gen_server:start({local, ?MODULE}, ?MODULE, [], []).
    
    relay(Nature, Message) ->
        gen_server:cast(?MODULE, {Nature, Message}).
    
    
    init(State) ->
        ok = z("Starting..."),
        {ok, State}.
    
    handle_call(Message, From, S) ->
        ok = z("Received ~tp from ~tp", [Message, From]),
        {reply, ok, S}.
    
    handle_cast({cast, Message}, S) ->
        ok = some_test:cast(Message),
        {noreply, S};
    handle_cast({call, Message}, S) ->
        ok = z("Sending call with ~tp", [Message]),
        Reply = some_test:normal_call(Message),
        ok = z("Received ~tp as reply", [Reply]),
        {noreply, S};
    handle_cast({infinite, Message}, S) ->
        ok = z("Sending infinite with ~tp", [Message]),
        Reply = some_test:infinite_call(Message),
        ok = z("Received ~tp as reply", [Reply]),
        {noreply, S};
    handle_cast(Message, S) ->
        ok = z("Unexpected ~tp", [Message]),
        {noreply, S}.
    

    Here are the relevant bits of some_test:

    start() ->
        gen_server:start({local, ?MODULE}, ?MODULE, [], []).
    
    normal_call(Message) ->
        gen_server:call(?MODULE, Message).
    
    infinite_call(Message) ->
        gen_server:call(?MODULE, Message, infinity).
    
    cast(Message) ->
        gen_server:cast(?MODULE, Message).
    
    % ...
    
    handle_call(normal, _, S) ->
        io:format("Inside the handle_call normal~n"),
        {reply, ok, S};
    handle_call(error, _, S) ->
        io:format("Inside the handle_call error~n"),
        {reply, 1/0, S};
    handle_call(bad_reply, _, S) ->
        io:format("Inside the handle_call error~n"),
        foo;
    handle_call(Message, From, S) ->
        io:format("Received ~tp from ~tp~n", [Message, From]),
        {reply, ok, S}.
    
    handle_cast(error, S) ->
        io:format("Bad arith: ~tp~n", [1/0]),
        {noreply, S};
    handle_cast(Message, S) ->
        io:format("Received ~tp~n", [Message]),
        {noreply, S}.
    

    Here is a run playing around with it. Note the output of the shell's own call to self() and flush():

    1> c(some_test).
    some_test.erl:31: Warning: this expression will fail with a 'badarith' exception
    some_test.erl:32: Warning: variable 'S' is unused
    some_test.erl:40: Warning: this expression will fail with a 'badarith' exception
    {ok,some_test}
    2> c(some_tester).
    {ok,some_tester}
    3> {ok, Test} = some_test:start().
    Inside the init ( <0.45.0> )
    Inside the init...
    {ok,<0.45.0>}
    4> {ok, Tester} = some_tester:start().
    <0.47.0> some_tester: Starting...
    {ok,<0.47.0>}
    5> monitor(process, Test).
    #Ref<0.0.2.178>
    6> monitor(process, Tester).
    #Ref<0.0.2.183>
    7> self().
    <0.33.0>
    8> some_tester:relay(call, foo).
    <0.47.0> some_tester: Sending call with foo
    ok
    Received foo from {<0.47.0>,#Ref<0.0.2.196>}
    <0.47.0> some_tester: Received ok as reply
    9> some_tester:relay(cast, bar).
    Received bar
    ok
    10> some_tester:relay(call, error).
    <0.47.0> some_tester: Sending call with error
    ok
    Inside the handle_call error
    Inside the terminate{badarith,
                            [{some_test,handle_call,3,
                                 [{file,"some_test.erl"},{line,31}]},
                             {gen_server,try_handle_call,4,
                                 [{file,"gen_server.erl"},{line,629}]},
                             {gen_server,handle_msg,5,
                                 [{file,"gen_server.erl"},{line,661}]},
                             {proc_lib,init_p_do_apply,3,
                                 [{file,"proc_lib.erl"},{line,240}]}]}
    Inside the terminate{{badarith,
                             [{some_test,handle_call,3,
                                  [{file,"some_test.erl"},{line,31}]},
                              {gen_server,try_handle_call,4,
                                  [{file,"gen_server.erl"},{line,629}]},
                              {gen_server,handle_msg,5,
                                  [{file,"gen_server.erl"},{line,661}]},
                              {proc_lib,init_p_do_apply,3,
                                  [{file,"proc_lib.erl"},{line,240}]}]},
                         {gen_server,call,[some_test,error]}}
    11> 
    =ERROR REPORT==== 3-Dec-2015::22:52:17 ===
    ** Generic server some_test terminating 
    ** Last message in was error
    ** When Server state == nil
    ** Reason for termination == 
    ** {badarith,[{some_test,handle_call,3,[{file,"some_test.erl"},{line,31}]},
                  {gen_server,try_handle_call,4,
                              [{file,"gen_server.erl"},{line,629}]},
                  {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,661}]},
                  {proc_lib,init_p_do_apply,3,
                            [{file,"proc_lib.erl"},{line,240}]}]}
    
    =ERROR REPORT==== 3-Dec-2015::22:52:17 ===
    ** Generic server some_tester terminating 
    ** Last message in was {'$gen_cast',{call,error}}
    ** When Server state == []
    ** Reason for termination == 
    ** {{{badarith,[{some_test,handle_call,3,[{file,"some_test.erl"},{line,31}]},
                    {gen_server,try_handle_call,4,
                                [{file,"gen_server.erl"},{line,629}]},
                    {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,661}]},
                    {proc_lib,init_p_do_apply,3,
                              [{file,"proc_lib.erl"},{line,240}]}]},
         {gen_server,call,[some_test,error]}},
        [{gen_server,call,2,[{file,"gen_server.erl"},{line,204}]},
         {some_tester,handle_cast,2,[{file,"some_tester.erl"},{line,24}]},
         {gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,615}]},
         {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,681}]},
         {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,240}]}]}
    
    11> self().
    <0.33.0>
    12> flush().
    Shell got {'DOWN',#Ref<0.0.2.178>,process,<0.45.0>,
                  {badarith,
                      [{some_test,handle_call,3,
                           [{file,"some_test.erl"},{line,31}]},
                       {gen_server,try_handle_call,4,
                           [{file,"gen_server.erl"},{line,629}]},
                       {gen_server,handle_msg,5,
                           [{file,"gen_server.erl"},{line,661}]},
                       {proc_lib,init_p_do_apply,3,
                           [{file,"proc_lib.erl"},{line,240}]}]}}
    Shell got {'DOWN',#Ref<0.0.2.183>,process,<0.47.0>,
                  {{badarith,
                       [{some_test,handle_call,3,
                            [{file,"some_test.erl"},{line,31}]},
                        {gen_server,try_handle_call,4,
                            [{file,"gen_server.erl"},{line,629}]},
                        {gen_server,handle_msg,5,
                            [{file,"gen_server.erl"},{line,661}]},
                        {proc_lib,init_p_do_apply,3,
                            [{file,"proc_lib.erl"},{line,240}]}]},
                   {gen_server,call,[some_test,error]}}}
    ok
    

    Read through that carefully.