[erlang-questions] gen_statem state enter call and changing t oanother state

Fred Hebert mononcqc@REDACTED
Wed Jun 13 02:55:58 CEST 2018


On 06/12, Raimo Niskanen wrote:
>Sorry - I do not follow your argument about postponing making sense for
>"state entered" but not for "entering state"...
>
>

Assume I have a state machine with 3 states:

 ,--------------------v
ready -> moving -> stopped-,
 ^-------------------------'

If the enter event is seen as taking place after the state transition 
has taken place, then a transition of the form:

ready -> stopped -> ready

where the transition from 'stopped -> ready' takes place with the 
'enter' event means that postponing should fire messages enqueued while 
in the 'ready' state even if you're back in the 'ready' state, since you 
went through a successful state transition to a state other than the 
current one.

On the other hand, if the 'enter' event is seen as happening before a 
transition, then the 'enter' event when getting to the 'stopped' state 
would instead be interpreted like:

ready ---x stopped
         |
         '---> ready

Where the enter event _prevents_ switching to the stopped state. In that 
case, then it would make sense not to de-queue events postponed during 
the 'ready' state.

This chain can be as long as you please though and you could imagine 
something like:

a -> b -> c -> b -> c -> b -> a

and if all those moves to 'c' or 'b' take place in 'enter' events, then 
that's an interesting edge case to handle.

>> As for allowing next_event in an enter event, I'm not a fan. If you
>> really need an event to run as the first thing in an enter event, just
>> run it in the enter event itself? Otherwise this lets you conceptually
>
>You lost me there...
>

THinking back again, this is not a significant point. You can disregard 
it.

>I decided to have the principal that activating or deactivating
>state enter calls should not affect event handling in any way.
>I.e state enter calls should be orthogonal to
>'postpone' and {next_event, ...}.
>
>Therefore you are now forced to insert events from the previous state, 
>and you get the inserted event(s) after the state enter call.
>

I'm happy with the current state of things.

>
>I agree.  State enter calls were introduced to allow code to be run when a
>state is entered, without having to duplicate that code at every state exit
>from the previous state.  It is a feature existing in other state machine
>description languages e.g OpenBSD's ifstated.
>
>The implementation now is for the interpretation of "state enter" as
>"having entered the state", which I think is the safest and least
>complicated interpretation.
>

Agreed.

>But the question about the limitations of state enter calls has come up
>some times now, so maybe it was time to have a second look...
>
>The limitations are geared towards state oriented code, in particular
>callback mode state_functions, so the question is if it would be possible
>to open up flexibility that would be useful for non state oriented code,
>without messing up?
>

I think the limitations should be the same whether you work with 
callbacks or with handle_event(...). They're different types of events.  
I wouldn't expect to use {reply, ..., ...} actions to a cast or internal 
event (an easy one since they don't carry a pid), and I shouldn't expect 
to postpone or next_event action on an enter event.

There's already plenty of context-sensitive information.

>>
>> Yes, that is an entirely valid concern. I think it is worth making the
>> existing typespecs a tad more complex to prevent user code from being
>> even worse than that.
>
>Can you elaborate on how to improve the typespecs for this?
>

I can't, not without repetition. Currently the biggest problem is that 
edoc has you jump around a bit to avoid repetition in the source code.  
For example, if I'm looking at handle_event, I have the following return 
signature:

HandleEventResult
HandleEventResult = event_handler_result(state())

Jumping to:

event_handler_result(StateType) =
    {next_state, NextState :: StateType, NewData :: data()} |
    {next_state,
     NextState :: StateType,
     NewData :: data(),
     Actions :: [action()] | action()} |
    state_callback_result(action())


which has me jump to action():

action() =
    postpone |
    {postpone, Postpone :: postpone()} |
    {next_event,
     EventType :: event_type(),
     EventContent :: term()} |
    enter_action()

which has me jump to enter_action():

enter_action() =
    hibernate |
    {hibernate, Hibernate :: hibernate()} |
    timeout_action() |
    reply_action()

... and so on.

I basically need to manually click links and jump through at least 3-4 
levels of type definitions to know what I am allowed to return or not.  
Note that I've also skipped over branching, as I have avoided expanding 
state_callback_result(action()), timeout_action(), and reply_action().

By comparison, if the repetition took place in the code rather than the 
doc (or if edoc could expand the type tree), then I could see a single 
signature for the thing.

It totally makes sense for the types to be defined that way in code, but 
they don't make for good documentation.

Regards,
Fred.



More information about the erlang-questions mailing list