[erlang-questions] gen_server & wait/notify

Chris Waymire chris@REDACTED
Mon Oct 2 00:49:21 CEST 2017


Ahhh.. I was not familiar with how the noreply response works from
handle_call. If I'm understanding it correctly though, in the snippet he
provided, I would need to change {reply, Message, NewState} to instead call
gen_server:reply/2 which then unblocks the caller. That is exactly what I
am needing. I figured there had to be something to do that without having
to cobble together some cludgy code.

Thanks.

-- Chris


On Sun, Oct 1, 2017 at 2:44 PM, Mikael Pettersson <mikpelinux@REDACTED>
wrote:

> See Vlad's reply a few days ago.  What you're describing is easily
> solvable by:
> 1. having the gen_server's handle_call for this API call stash away the
> From and uuid in its internal state and return noreply as that blocks the
> caller, and
> 2. when the response comes in (tagged with that uuid) match it to the
> corresponding From and gen_server:reply to that one, which unblocks the
> caller.
>
>
> On Sun, Oct 1, 2017 at 7:22 PM, Chris Waymire <chris@REDACTED> wrote:
>
>> I'll try to provide some context around what I am doing. Basically I am
>> writing a client for a custom MQTT network that in addition to the basic
>> MQTT functionality also provides a request/response system. So the idea
>> here is that when a user of the app executes the "request" function a uuid
>> will be attached to their message payload and published onto the network.
>> Another endpoint that is subscribed to that topic will receive the message,
>> process it, and publish a response with a reference to the original message
>> id. The original sender then picks up this message and accepts it as the
>> response to their request. There's more going on there but that's the gist
>> of it. So which the request function is called I need the caller to block,
>> but not the MQTT client itself as it will still need to be processing
>> messages on the network.
>>
>> On Fri, Sep 29, 2017 at 12:40 AM, zxq9 <zxq9@REDACTED> wrote:
>>
>>> 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
>>> _______________________________________________
>>> erlang-questions mailing list
>>> erlang-questions@REDACTED
>>> http://erlang.org/mailman/listinfo/erlang-questions
>>>
>>
>>
>> _______________________________________________
>> erlang-questions mailing list
>> erlang-questions@REDACTED
>> http://erlang.org/mailman/listinfo/erlang-questions
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20171001/9a11fb3b/attachment.htm>


More information about the erlang-questions mailing list