Not an ID
Not an ID

Reputation: 2589

Can receive clause be used in a gen_server process?

Is it OK to use receive clause in a gen_server process? I'm reading Chapter 10 of Designing for Scalability and it says:

enter image description here

Is there any reason that makes the author say so? I know if we want to communicate with a gen_server, we should gen_server:call/cast, but what if in our handle_call/cast part, we need the power of a receive clause? Is it OK to use it?

Upvotes: 1

Views: 1040

Answers (2)

legoscia
legoscia

Reputation: 41528

Pascal's answer tells you why using a receive in handle_call and other callbacks may or may not be a good idea. Let me just note that it seems that that was not what the author meant with that sentence. The point being made is that if you expect two messages, A and B, and you're not sure which one will arrive first, but you want to process A before B, you can easily do that with receive:

wait_for_a() ->
    receive
        {a, A} ->
            process_a(A),
            wait_for_b()
    end.

wait_for_b() ->
    receive
        {b, B} ->
            process_b(B)
    end.

However, if your process is a gen_server or a gen_fsm, you can't do anything like that: your callback functions will be called with the incoming messages in order, and if you want to defer message B for later processing, you have to save it in your state or something.


Another thing to consider when using receive in handle_call / handle_cast is that you might receive system messages, which in theory you should be prepared to handle.

Upvotes: 3

Pascal
Pascal

Reputation: 14042

By default the gen_server call (and gen_fsm) has a time-out of 5 seconds. If a callback function lasts too long, then the server crashes with the reason

{timeout,{gen_server,call,[GenServerPid,LastMessage]}}

It doesn't seems that the cast function has the same time-out (I guess because the callback returns immediately), but it will cause the next call to fail if it is blocked by the callback execution.

So I don't think it is a good idea unless your application requires that a message arrives shortly during the callback (in other words if you consider that the lack of the second message is an error condition)

check the following code:

-module (tout).

-behaviour(gen_server).

-define(SERVER, ?MODULE).

%% export interfaces
-export([start_link/0,call/2,cast/2]).

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

%% INTERFACES %%

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

call(Pid,Time) ->
    gen_server:call(Pid,{wait,Time}).

cast(Pid,Time) ->
    gen_server:cast(Pid,{wait,Time}).

%% CALLBACK FUNCTIONS %%

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

handle_call({wait,Time}, _From, State) ->
    timer:sleep(Time),
    {reply, done, State};
handle_call(_Request, _From, State) ->
    {reply, {error, unknown_call}, State}.

handle_cast({wait,Time}, State) ->
    timer:sleep(Time),
    {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}.

%% LOCAL FUNCTIONS %%

Shell session:

1> c(tout).                     
{ok,tout}
2> tout:start_link().           
{ok,<0.136.0>}
3> tout:call(tout,500).         
done
4> tout:call(tout,5100).        
** exception exit: {timeout,{gen_server,call,[tout,{wait,5100}]}}
     in function  gen_server:call/2 (gen_server.erl, line 204)
5> tout:start_link().   
{ok,<0.141.0>}
6> tout:cast(tout,10000).
ok
7> tout:cast(tout,1000). 
ok
8> % wait a little .
8> tout:call(tout,100).  
done
9> tout:cast(tout,10000). 
ok
10> % no wait .
11> tout:call(tout,100).  
** exception exit: {timeout,{gen_server,call,[tout,{wait,100}]}}
     in function  gen_server:call/2 (gen_server.erl, line 204)
12> 

[edit] Yes selective receive is not possible with gen_fsm and the usual problem is:

Fsm is in state_1 and receives a message that should be handle in state_2, just before the message asking to go to state_2 arrives then:

  • this first message must be handled in state_1 otherwise the process crashes,
  • this message is removed from the mailbox and will not be here when the fsm will switch to state2, so the application must manage this risk (for example avoid the situation by sending the 2 messages in the right order by one single process).

This situation cannot be solved by a receive block in one of the callback since this issue appears between 2 callbacks, while the process is executing the gen_fsm code.

I think it is one of the trouble that should be solved by the new behavior gen_statem coming in the R19 (I read it very fast though)

Upvotes: 2

Related Questions