[erlang-questions] gen_server & wait/notify

zxq9 <>
Fri Sep 29 09:40:01 CEST 2017


On 2017年09月29日 金曜日 16:23:42 zxq9 wrote:
> On 2017年09月28日 木曜日 17:58:08 Chris Waymire wrote:
> > handle_call({request, Data#data{uid=UID}}, _From, State) ->
> >     make_asycnc_req(Data),
> >     receive
> >         {UID, Response} -> {reply, Response, State}
> >     end.
> > 
> > handle_info({response, Response#response{uid=UID}}, State) ->
> >     self() ! {UID, Response},
> >     {noreply, State}.
> 
> 
> Your naked `receive` will work just fine. The flow of the process has never
> returned to the gen_server module yet to await another message to dispatch,
> so when you write a naked `receive` in some handling code that is exactly
> where the process will block, and you can receive any arbitrary thing you
> want there. Just be careful not to match on any system or gen_server message
> types and you'll get the behavior you expect (though your mailbox may be
> filling up with other stuff in the meantime).

One note of caution is that you might actually be fighting against the
natural order of things. Consider carefully whether you need this and what
it is achieving for you. You may really be better off with casts. See if
there is a way for you to design the system to be entirely async, and if
not, why not. What state is being async going to threaten? Is it an
ordering issue? Are you relaying a message and waiting for a response
that will be returned? If so, why is the originating process not just
receiving the response directly? (Do you really need a middle-man?)

Another way to achieve this without blocking is to use references to
tag messages and keep a digest of them. That prevents your gen_server
from becoming unresponsive to system messages or blocking indefinitely
in the event the sender of the message you are waiting on crashes.

This is actually what the `From` argument to handle_call/3 is for:

handle_call({request, Data = data{uid = UID}},
            {Sender, Tag},
            State = #s{queue = Q}) ->
    ok = make_async_request(Data, Tag),
    {noreply, State#s{queue = [{{Tag, UID, Sender} | Queue]}};
% ...

handle_cast({response, {Tag, UID}, Message}, State = #s{queue = Queue}) ->
    case lists:keyfind(Tag, 1, Queue) of
        Ticket = {Tag, UID, Sender} ->
            NewState = State#s{queue = lists:delete(Ticket, Queue)},
            {reply, Message, NewState};
        false ->
            LogString = "Received bad message: ~tp",
            ok = log(warning, LogString, [{{Tag, UID}, Message}]),
            {noreply, State}
    end;
% ...

There may be any number of ways you might want to phrase that or structure
it, but basically that's how you can safely stow such a pending response
value and sender, and get the response back out to them without any weird
blockages or unresponsiveness.

-Craig


More information about the erlang-questions mailing list