Gen_statem receiving data

Yeah, I just added all that as notes to my first example right before you posted this :smile:

Yeah, itā€™s conflictingā€¦ on one hand, it seems best to say ā€œDonā€™t ever do thatā€, OTOH yet thereā€™s room for sharp knifes, and valid at that.

Thatā€™s why I think we can probably make a chapter on the dos and donts of gen_* and using primitives in them.

2 Likes

This is where async comes in handy. So you send a messageā€¦ (optimally not using send/2), then you can handle that as misc info event later (thatā€™s only one way to do it).

But I guess that begs the questionā€¦ in what you were trying to acheive is the call to a remote process (and perhaps on another node) just a side effect or should it effect state in the state machine? That was not clear.

2 Likes

That just doesnā€™t feel right (no offense). Manually receiving and handling the parent EXIT and system messages, and having to implement the system_* callbacks appears to belong to a level below gen_*, in the realm of special processes and such, something gen_* was made to build on and abstract from.
(For context, I didnā€™t even know about system messages and the like before I happened to fiddle around in ranch, where ranch_conns_sup is some sort of streamlined custom supervisor implemented as a special process, which accordingly has to deal with system messages itself.)

Btw, I just noticed that there is a flaw in the code that you posted above.

The second and third clauses are superseded by the first, and can never be reached. But this shows just how error-prone this approach is, so Iā€™m pretty sure this is not the way how things should be done.

2 Likes

A starting point : Adjust language around the usage of receive in a gen_statem. by starbelly Ā· Pull Request #6294 Ā· erlang/otp Ā· GitHub

4 Likes

@Maria-12648430 it was just an example, of course in real code I will let Reply to be the last clause

1 Like

I just want to confirm @Maria-12648430ā€™s and @starbellyā€™s conclusions.

The documentation that states that you ā€œcannot use selective receiveā€ is over-simplifying trying to convey the mindset that when using a gen_* behaviour you should handle general received messages as info messages in a behaviour callback.

It is also a confusing wording since it uses ā€œselective receiveā€ as a name for the Erlang language receive mechanism, when, actually, selective receive is the receive variant that is possible to use (and that is used whenever you call some other gen_* behaviour client API using e.g gen_statem:call/2 which sends a message and does a selective receive). You can do almost anything that does not block too long.

You cannot call sys:handle_system_message/6,7 from your behaviour callback. It is supposed to only be called from the proc_lib processā€™ (in this case gen_statemā€™s) receive loop. If called from your callback module you hijack the receive loop and get an utter mess.

So, as said, it is ā€œreceive allā€ that cannot be used in a gen_* behaviour callback.

I suspect you either should use selective receive in the same manner as e.g gen_statem:call/2 or that you want to implement something like gen_statem:send_request/2,4, gen_statem:check_response/2,3 and friends that solve the problem of how to call a server that blocks too long, by doing it asynchronously. Send a request which returns a request id, record the request id your state, later when handling an info message check if it is the response corresponding to the request id. With gen_statem you have the possibility to postpone a message that is not the response you were looking for.

6 Likes

The conclusion that I had from this topic is the answer to the question : why erlang atom ? itā€™s not simple to know the exact answer

You lost me there

4 Likes

trust me, itā€™s a tricky question, I mean everything know atoms and how to use them but why atom is so deeper I hope you can understand that

As a (so far) read-only member of this thread my takeaway would be

  • if using behaviours, do not mess with the receive loop
  • if non-behaviour receives are needed, handle them with info

And, not completely serious, the answer to why erlang atom is 42.

1 Like

and

  • Selective receive where you receive only exactly the message you want and do not wait for ā€œtoo longā€ is ok.
3 Likes

No Iam serious, if you see that not serious I think @dischoen you should answer this : why not using strings (list of bytes) to represent what we represent as atom for example : reply will be "reply" and we have more perfomance using binaries ?

Iā€™m curious :thinking:
Iā€™ll bite :fishing_pole_and_fish:
Why?

2 Likes

As my understanding, itā€™s the atom of an erlang message, itā€™s the only thing that canā€™t be found on 2 differents erlang messages

@Maria-12648430 I will upload a little server in github within few days that may help me to get a job, I want your support and all my brothers here

Hm, notā€¦ exactlyā€¦ (but I may just misunderstand, correct me if so).

Atoms are nice to tag a message to indicate the purpose or type of a message, but they certainly do not make a message unique. In a nutshell, this means that they are not the best fit to (reliably) correlate requests and responses.

For the purpose of tagging requests and the respective replies, you should use references. References are, for all practical reasons, created unique (not strictly, but even if a scheduler thread did create a new reference every nanosecond, they would be reused only after about half a millennium, IIRC).

For example, imagine the following (contrived):

F=fun() -> receive {request, ReplyTo, {A, B}} -> ReplyTo ! {reply, A+B} end end,
P1=spawn(F),
P2=spawn(F),
P1 ! {request, self(), {1, 2}},
P2 ! {request, self(), {3, 4}},
Sum1 = receive {reply, S1} -> S1 end,
Sum2 = receive {reply, S2} -> S2 end.

In the example above, it could be that Sum1 is 3 and Sum2 is 7, but it could just as well turn out the other way round. It is indistinguishable what requests the replies belong to.

It is better done like this (equally contrived):

F=fun() -> receive {request, {ReplyTo, Tag}, {A, B}} -> ReplyTo ! {reply, Tag, A+B} end end,
P1=spawn(F),
P2=spawn(F),
Tag1=make_ref(),
Tag2=make_ref(),
P1 ! {request, {self(), Tag1}, {1, 2}},
P2 ! {request, {self(), Tag2}, {3, 4}},
Sum1 = receive {reply, Tag1, S1} -> S1 end,
Sum2 = receive {reply, Tag2, S2} -> S2 end.

This way, the replies can be correlated to the requests using their Tag* references, so Sum1 is always 3 (reply to the first request), and Sum2 is always 7 (reply to the second request), even if they arrive in reverse order.

Btw, you donā€™t have to restrict yourself to either atoms or references, you can mix and mingle as needed.

gen:call is a nice example for all the above.

Finally, why do we like to use atoms (vs lists or binaries), here and everywhere else in Erlang? Because atoms are tiny and fast by design (and easily recognizable in code, other than integers would be), just 1 machine word. @garazdawi explained it nicely here. Lists and binaries are much more expensive to throw around.

3 Likes

@Maria-12648430 You misunderstand me or maybe I hadnā€™t describe it as I thinked, you see I said that itā€™s not simple as appear :grinning:
I mean by differents erlang messages the messages that are differents in their atom (I mean by atom here the real atom and not erlang atom),
Of course I didnā€™t mean each erlang message have itā€™s atom thatā€™s not logic but when using the reply atom that means that the message is a reply, it canā€™t be a request or a system message or last will from parent, the receive will catch always reply messages and postpone other messages, and when we use the ack packet atom we will catch only acknowledge packets and postpone everything else I can see how erlang is beautiful in itā€™s way to handle thatā€¦ so 2 differents messages means two atom-different messages, that is, we can receive from a server differents types of messages but using atoms will be the golden way to never catch something by mistake, I hope this will clarify things

An atom is a reference to a constant. Itā€™s just one word in size, and very fast to test for equality. Binaries are bigger, and to test for equality you need to compare the characters one by one. So pattern matching binaries (or charlists) is slower than atoms.

2 Likes

Excellent as usual, thank you @domi, I think I should get a minimal learning to machine structure and memory representation (I did it at the university but I forgot everything about)

Just another thing if you can answer @domi, "reply" as string is coded via 5 bytes each byte represent the ASCII code of each character but I canā€™t imagine how the atom reply is coded if we donā€™t code it as above ?