tags:

views:

371

answers:

5

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.

+2  A: 

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.
Zed
Just for the sake of completeness, you could resend all unwanted messages to yourself (thus moving it to the end of your mailbox). This has many problems (your gen_server will work on full throttle thowing messages back and forth; it uses implementation specifics), so you should NOT use it EVER :)handle_cast(Msg, State) -> self() ! {'$gen_cast', Msg}, {noreply, State}.
Zed
Yeah, I considered this earlier. Very briefly. ;)Your first idea is a good one. The usual case is that I need to collect 8 items before processing them. There'll typically be 200 batches of these in a job. I'm trying to calculate whether your method here is more efficient than tagging each result with a number and sorting each batch once 8 items have been received.
mwt
+1  A: 

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.

Christian
The gen_fsm is an interesting idea--I've been looking for an excuse to use it. At first blush, I don't think it'll work for me, unless I could "parameterize" the states somehow. The typical case is 8 states, but there are edge cases with hundreds. Don't know if gen_fsm was meant for that.I really, really want the OTP features, so I'll handle the selective receiving in some other way given that gen_servers apparently can't do it naturally.
mwt
+1  A: 

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.

Jeremy Wall
+4  A: 

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?

rvirding
If you mean which OTP features, well, I like the debugging stuff, like sys:get_status. That's already been really handy. And the crash reports that come from sasl have been invaluable. And I want the processes to be supervised, although maybe that's just as easy with ordinary processes? And I kinda sorta liked the idea of calling my processes with the gen_server api. The biggest thing is just that this is my first major erlang project and it's on a deadline and OTP seems like the Right Way. Not having the time to explore every cranny, I want to make sure I get all built in advantages.
mwt
That's to say, I want to conform to the application framework as much as possible so I can understand what the advantages are. Also, I haven't got to the release upgrade handling yet, and I expect there is an advantage to using gen_servers for that.
mwt
+3  A: 

"plain_fsm" will allow you to do selective receive while still being OTP compliant.

http://github.com/esl/plain_fsm

goertzenator