Reputation: 2589
Is it OK to use receive
clause in a gen_server process? I'm reading Chapter 10 of Designing for Scalability and it says:
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
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
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 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