Gen_statem receiving data

Hello, consider we have a function that communicate with a remote server and returns its data as follows :

send_to_server(Msg) ->
erlang:send(RemoteRef ,  Msg),
receive
Reply -> Reply
end. 

and we should use it constantly in our program, for example :

Reply0 = send_to_server(Msg0), 
Res0 = handle(Reply0), 
Reply1 = send_to_server(Res0), 
.... 

how can we implement this function within a gen_statem since receive is not authorised in any gen_* behaviour ?

I’m not sure I fully understand. You can indeed you receive inside any gen_* behavour, whether or not that’s a good idea or not is another question. You can use send and receive, gen, erpc, etc. You can do things asynchronously as well, as to not block (i,e, do a send, then handle the reply in a catch all handle_event clause). You can also spawn a process to do this for you, get the reply, and send it back to your gen_statem, once again mostly likely handling in a handle_event catch all.

Without knowing what it is you’re trying to build it’s hard to say what a good pattern here it, what’s more what you’re trying to do may dictate that gen_statem is not a good fit here, but simply can’t say without details.

2 Likes

Thank you @starbelly,
But if I changed the current state to catch the message how can I return to the rest of code ? I just want a portable function like that I mentionned to call it when I need
Spawning another process don’t resolve the problem because you still need to receive from it

1 Like

Fist of all, you can use receive in a gen_statem, just make sure you don’t consume the “internal” gen_* messages, so probably it’s better to send {my_msg, Msg} instead of Msg and receive {my_msg, Reply} instead of Reply.

You can receive messages in a gen_statem with the Module:Statename/3 or Module:handle_event/4 function where the first parameter is info too - maybe this is what you’re after.

2 Likes

I think what is not understand is whether the send and recv needs to be blocking. As pointed out, this is generally not the best way to handle a situation like this, but as is tradition it depends.

However, let’s say for arguments sake, this is what must be done and we need to do it using primitives vs gen*. or erpc, pg, etc.

handle_event({call,From}, some_event, State) ->
    erlang:whereis(some_server) ! {self(), ping},
    receive
        pong ->
            {keep_state,State,[{reply,From,hello]};
        _ -> 
             %% This is dangerous, a system message could come in and get dropped on the floor. 
             %% A better form would be without the catch all, but we'd still be blocking for 
             %% up to 5 seconds, which could have a nasty ripple effect across the system. 
             {keep_state,State, [{reply, From, {error, "Unexpected message for ping from remote server"}}]}
    after
        5000 ->
            {keep_state,State, [{reply, From, {error, "timeout waiting for remote pong"}}]}
    end;

You could also change state here if you liked.

Then in our some_server…

handle_info({From, ping}, State) ->
    From ! pong,
    {noreply, State};

Mission accomplished. As mentioned by others, you may need / want to handle this async, in which case you can do a send, don’t send a reply in the handle_event/4, then handle the reply from the remote later on (if one comes at all), and finally do a reply to the client.

You can spawn a process to do this which sends the message to the remote than sends a message, awaits the reply, then sends it back to your gen_statem process. You can also simply send the message via send, gen_server:cast/3, etc. All depends.

Either way, it’s still impossible to say what’s best for your situation without knowing what it is you’re trying to build and what you’re trying to do in this specific scenario. Further, what is it your sending a message to remotely? Is it a bare bones process? Is it a gen_server? The answer to that question really dictates the best way to actually do the send itself (e.g., if it’s a gen_server, you gen_server:call/3 etc.)

2 Likes

No I don’t need to block, it was just an example but the real code include of course timeouts.
You can’t use receive in standard behaviours

1 Like

You in fact can, but doesn’t mean you should, as pointed out as an example, here’s an async example (contrived), utilizing the push button example from the docs:

-module(pushbutton).
-behaviour(gen_statem).

-export([start/0,push/0,get_count/0,stop/0]).
-export([terminate/3,code_change/4,init/1,callback_mode/0]).
-export([on/3,off/3]).

name() -> pushbutton_statem. % The registered server name

%% API.  This example uses a registered name name()
%% and does not link to the caller.
start() ->
    gen_statem:start({local,name()}, ?MODULE, [], []).
push() ->
    gen_statem:call(name(), push).
get_count() ->
    gen_statem:call(name(), get_count).
stop() ->
    gen_statem:stop(name()).

%% Mandatory callback functions
terminate(_Reason, _State, _Data) ->
    void.
code_change(_Vsn, State, Data, _Extra) ->
    {ok,State,Data}.
init([]) ->
    %% Set the initial state + data.  Data is used only as a counter.
    State = off, Data = 0,
    {ok,State,Data}.
callback_mode() -> state_functions.

%%% state callback(s)

off({call,From}, push, Data) ->
    %% Go to 'on', increment count and reply
    %% that the resulting status is 'on'
    {next_state,on,Data+1,[{reply,From,on}]};
off(EventType, EventContent, Data) ->
    handle_event(EventType, EventContent, Data).

on({call,From}, push, Data) ->
    %% Go to 'off' and reply that the resulting status is 'off'
    {next_state,off,Data,[{reply,From,off}]};
on(EventType, EventContent, Data) ->
    handle_event(EventType, EventContent, Data).

%% Handle events common to all states
handle_event({call, From}, get_count, Data) ->
    erlang:whereis(ping_pong) ! {self(), ping, From},
    {keep_state,Data};
handle_event(info, {pong, OrigFrom}, Data) ->
    gen_statem:reply(OrigFrom, Data),
    {keep_state, Data};
handle_event(_, _, Data) ->
    %% Ignore all other events
    {keep_state, Data}.

This is a bad example, but demonstrates a thing none the less. Particularly bad because we’re relying on passing the client pid to the ping_pong process and getting it back later so we can reply to the client, but yeah, demonstrates a thing.

Likewise, you can do the same thing but spawn a proc you pass the client pid to, does a send/recv, then signals back to your gen_statem using send again.

2 Likes

… especially because it does not work as posted (omits the on/3 and off/3 callbacks). Here is the link to the docs that contains the full example: https://www.erlang.org/doc/man/gen_statem.html#example
But I don’t see what it is that it should demonstrate, in the context of this discussion?

… but you can, what makes you think you can’t? I just happen to be taking part in an PR to OTP that happens to do just that: a receive in a standard behavior (albeit in a gen_server, not a gen_statem).

However, because you can doesn’t mean you should, not unless you know what you’re doing. If you’re not careful, you may steal messages that were meant for some entirely different purpose, but that is a slightly different topic.

Be that as it may, I too find it very difficult to answer your question, because I simply don’t understand what you are trying to do, and how :sweat_smile: It would help a lot if you could be more elaborate, maybe show some code even.

3 Likes

It merely demonstrates you can use receive in callback on gen_statem, that’s it. You can do it in a blocking manor or a non blocking manor… and yes it was pointed out this is probably a bad idea.

P.S. I just stuck in on get_count event for absolutely no particular reason.

Exactly.

Ditto.

2 Likes

Oh, so that refers to code given in your earlier post. That confused me (since there is no receive in the pushbutton example) :sweat_smile:

2 Likes

hehe yeah, I suppose that wasn’t helpful :slight_smile: Maybe I should put a more proper example for future readers, I was attempting to expend as little effort as possible : copy → pasta → puke to show mechanics :laughing:

3 Likes

Maybe if @Abdelghani could provide more information, we could answer more to the point :wink: I have to admit that I still don’t know what we’re really talking about here :sweat_smile:

2 Likes

I think I blame myself… After trying to nudge that information out, I resorted to infer, and everything went wrong there :stuck_out_tongue:

3 Likes

Thank you all for your help but I don’t know how can the official guide of the gen_statem not been read by the most of erlang developers that have used that engine before ? sorry but Iam just wonder.
Okey I think I found the answer to this, I will just include system messages and parent exit in the receive block

do_receive(Parent) ->  %% Parent is the pid of the process's parent
receive
        Reply ->
                Reply;
        {'EXIT', Parent, Reason} ->
                {stop, Reason};
        {system, From, Request} ->
                sys:handle_system_msg(Request, From, 
                Parent,?MODULE, [], Parent)
after 5000 ->
        .... 
end. 

system_continue( _ , _ , Parent) ->
do_receive(Parent). 
system_terminate( Reason , _ , _ , _) ->
{stop, Reason}. 
system_code_change(Misc, _ , _ , _) ->
{ok, Misc}. 

send_to_server(Req, ServerRef, Parent) ->
erlang:send(ServerRef, Req), 
do_receive(Parent). 

Now we can use this portable function freely

Reply0 = send_to_server(Req0, ServerRef, Parent), 
Reply1 = send_to_server(Req1, ServetRef, Parent), 
.... 

From erlang.org
" A selective receive cannot be used from a gen_statem behaviour (or from any gen_* behaviour), as the receive statement is within the gen_* engine itself. It must be there because all sys compatible behaviours must respond to system messages and therefore do that in their engine receive loop, passing non-system messages to the callback module" .

1 Like

Heh :wink: I don’t think even a pastor would know each and every passage of the bible by heart, that’s what the book is for :grin:

We know better! :innocent: (just joking :grin:)

(Specifically: Erlang -- gen_statem Behaviour)

You’re right, that is written there :wink:

But also, strictly speaking that is wrong. receive, selective or not, can be used in a gen_statem (and any gen_* behavior for that matter). Otherwise, that process simply could not talk to anything else, whatever it may be. For example, if you call something like timer:apply_after(1000, erlang, make_ref, []) (pointless as that may be, I use it just to illustrate a point) in your gen_statem, yes, you’re not writing a receive there, but nevertheless there will be one. timer:apply_after/4 ultimately does a gen_server:call/4 (to the timer server), which defers to gen:call, which in turn defers to gen:do_call. And gen:do_call sets up a monitor (with alias, since OTP 24) on the target process, wraps its Pid (as the reply target) (or the reply alias, since OTP 24) and monitor reference (as a tag) in a tuple, sends that to the target process, and then does a (selective) receive, waiting for either a reply tuple containing the tag, a 'DOWN' message containing the monitor reference, or a timeout.

Sorry for the lengthy explanation :sweat_smile: I just want to avoid further misunderstandings.

So you see, there are receives happening all the time, but they’re usually hidden behind several layers of API. It’ s simply not a good idea to use them directly (that’s what the handle_info or handle_event(info, ...) callbacks are for), as it is easy to mess up things that way, which is not trivial to debug…

3 Likes

The docs should probably be updated to say “should not in general”, as well as provide examples of why you should not (my first example here is a good candidate for that), and if you do here is a good form for doing so, yet you should still prefer x,y,z.

Edit:

There’s one other thing to point about that particular section in gen_statem doc. The context seems to be around an selective receive with no timeout. Doing so in a callback would of course block forever, thus preventing system messages and the like from ever arriving.

3 Likes

@Maria-12648430 you have right, I didn’t pay attention that some of OTP internal functions contain receive block like gen_server:call but here this is not my mistake I have just followed what the documentation said

2 Likes

Of course not, and I didn’t mean it that way :hugs:

2 Likes

Hm, yes, but there is still more to it…

Even if there is a timeout, it may be a quite long one, so even if you would not wait forever, it could still be for a long time.

Another thing that I find misleading about the paragraph is that it seems to imply that receives are ok (like, “nothing can go wrong”) as long as they are not selective. But that is not the case, a non-selective receive could lead to system messages to be ignored/lost instead. For example…

handle_event(...) ->
    receive
        expected_message -> do_stuff();
        _Unexpected -> ok
    end,
    keep_state_and_data.

System messages (and whatever gen_statem-internal messages are zipping around, no idea what makes that monster tick :sweat_smile:) would end up in the second clause, and be lost.

So I guess the general advice should be, don’t use (manual) receive inside gen_* behaviors unless (you think) you know what you’re doing and there is no other way, otherwise absolutely stick to the callbacks.

2 Likes

But the appropriate answer to such situation was that the gen_statem returns to handle system messages or parent exit after finishing the receive block in its natural engine loop.