Reputation: 707
I ported most of my app to OTP behaviors, but I'm stuck. I can't figure out how to do selective receives using a gen_server. If none of the callback function clauses match a message, rather than putting the message back in the mailbox, it errors out!
Now, everywhere I go, folks laud selective receives. Everywhere I go, folks laud OTP. Can it really be true that you can't have both at once? Doesn't this seem like a major, correctable shortcoming?
How do erlang programmers handle this?
EDIT (responding to zed's comment):
Here's an example where I'd like to see a list of integers printed in sorted order:
-module(sel_recv).
-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([test/0]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
test() ->
gen_server:cast(?MODULE, test).
init([]) ->
{ok, 0}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(test, _State) ->
lists:map(fun(N) ->
gen_server:cast(?MODULE, {result, N})
end, [9,8,7,6,5,4,3,2,1]),
{noreply, [1,2,4,5,6,7,8,9]};
handle_cast({result, N}, [N|R]) ->
io:format("result: " ++ integer_to_list(N) ++ "~n"),
{noreply, R}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Of course, in my real app, there are timer delays and the messages that need to be processed in order are interleaved with other messages. In particular, I send out http requests, sometimes many at once, sometimes one at a time with an interval between them. In any case, I need to collect them in order.
Upvotes: 7
Views: 2575
Reputation: 2039
"plain_fsm" will allow you to do selective receive while still being OTP compliant.
http://github.com/esl/plain_fsm
Upvotes: 7
Reputation: 20916
No, gen_server is not designed to be able to handle selective receives, each request is processed as it arrives. This is actually a difficult problem as Erlang requires that all patterns are known at compile time, there is no "pattern object".
I agree that gen_fsm is probably not for you either as it doesn't take may different messages arriving in any order before you would get an explosion in the number of states. This was one of the reasons we added selective receives, it allows you to safely ignore uninteresting messages, leaving them for later.
Which OTP are you especially interested in?
Upvotes: 4
Reputation: 25237
Just because you can't use gen_server for one of your modules doesn't mean you aren't using OTP. All of the callback modules implement the receive block for you which prevents you from using selective receives. There is no reason you can't implement your own service which handles selective receives. And doing so doesn't mean you haven't done it the OTP way.
You can still have your service managed by a supervisor with all the benefits that affords.
Upvotes: 2
Reputation: 9486
Maybe you really want to use gen_fsm. That behaviour is usually chosen front-ends for protocols, where the protocol have certain states and need to handle requests differently depending on what state it is currently in.
But back to gen_server, we use gen_server for its features. It subscribes to system events for code-loading and gives you the code_change callback. It causes sasl-reports on crashes. You get a well known structure that helps code maintenance. Most importantly, it implements a well designed protocol for synchronous calls.
It is hard to say if OTP behaviors are right for you in this instance.
Given the comment, it sounds like a gen_server is wrong for you. When I use the gen_server behaviour I think of it as a boss in a company. Everyone wants to talk with the boss, so it is in the company's best interest to make the boss able to delegate jobs quickly and efficiently so that he doesn't let people sit waiting instead of working.
The gen_server can delegate using the 'From' parameter. To do that, return {no_reply, State}
and pass the From parameter to the delegate. The delegate uses gen_server:reply/2
to answer the original call.
This method of using a gen_server could possibly be for you. Start a "plain" process that you use receive-end in to do selective receive. If this is a truly independent job the gen_server can ignore it (fire and forget). If it wants to know if it is finished one can gen_server:cast/2
a message back to it from the started "plain" process.
If you want to block the gen_server while doing the selective receives, then it could be an idea to start a process as well and wait for it to die before returning. Selective receive is an O(n) linear seek on the messages in the order they arrived. So each selective receive will scan all the queued up messages which could be high for a popular gen_server.
No company is supposed to only have bosses working there. No erlang application is supposed to only have gen_servers.
Upvotes: 3
Reputation: 57648
Gen_server is probably not the best choice for this. One thing you can do is to receive all messages into a buffer list, and implement the selective receive yourself:
handle_cast(test, _State) ->
...
{noreply, {[1,2,4,5,6,7,8,9], []}};
handle_cast({result, N}, {Wait, Buff}) ->
{noreply, handle_results(Wait, [N|Buff])}.
handle_results([], Buff) ->
{[], Buff};
handle_results([W|WTail] = Wait, Buff) ->
case lists:member(W, Buff) of
true ->
io:format("result: " ++ integer_to_list(W) ++ "~n"),
handle_results(WTail, Buff -- [W]);
false ->
{Wait, Buff}
end.
Upvotes: 4