gen_statem state enter call and changing t oanother state

classic Classic list List threaded Threaded
21 messages Options
12
Reply | Threaded
Open this post in threaded view
|

gen_statem state enter call and changing t oanother state

Frans Schneider-2
Dear list,

the state_enter_result(State) has as one its return values {next_state,
State, NewData, ...} but explicitly disallows the State to be different
from the current State,  First, I find it confusing to allow
'next_state' here since State cannot change but the name makes the
suggestion it can. Secondly, I would love to be able to actually make a
state change. Quite often i find myself running a few tests on entering
a state and making state changes based on these tests. Now I have to
move these tests to all states which can have this state as a result and
go directly to the end state.

Would it be possible to change state from a state enter call?

Frans

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
On Mon, Jun 11, 2018 at 09:43:02AM +0200, Frans Schneider wrote:
> Dear list,
>
> the state_enter_result(State) has as one its return values {next_state,
> State, NewData, ...} but explicitly disallows the State to be different
> from the current State,  First, I find it confusing to allow
> 'next_state' here since State cannot change but the name makes the

Throughout gen_statem is is by design so that there is no difference
between {next_state, SameState, NewData, ...}
and {keep_state, NewData, ...}.  It is {keep_state, ...} that is shorthand
for {next_state, SameState, ...}.

So dissalowing {next_state, ...} in a state_enter call would be an
exception to the main rule that {next_state, ...} is the generic form.

There might be situations where you have a helper function that calculates
the next state so it would want to always return {next_state, ...}, and
then you want to use it in a state_enter call context knowing it will
always return {next_state, SameState, ...}.  I think this use should be
possible.

> suggestion it can. Secondly, I would love to be able to actually make a
> state change. Quite often i find myself running a few tests on entering
> a state and making state changes based on these tests. Now I have to

The state_enter call is intended for code to be executed when entering a
state, making this code co-located in the source with the state's event
handling code.

If the state_enter call should be allowed to change states it would no
longer be co-located since it would for example start a timer for the other
state it changes to.

The alternative to using state_enter calls would be to have a convention
where you at exit from the other state go through a helper function
that does what you need for state entry, and remember to use this
function for every state entry to the state in question.

I think your use case is a good example for when having a specialized state
change function makes sense.  There is no single state this kind of code
clearly should be co-located with.

> move these tests to all states which can have this state as a result and
> go directly to the end state.
>
> Would it be possible to change state from a state enter call?

I do not like that idea, for the reasons above...

>
> Frans

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Frans Schneider-2
Thanks Raimo,

See your point, but I am not yet really convinced.
Using a helper function would in my case.opinion  make the code more
obliterative. I think the following code makes it very clear what goes
on while some helper function called from whatever other place would
make things much more obscure.

handle_event(enter,
             _Old_state,
             #state{reliability_level = reliable,
                    state1 = announcing} = State,
             #data{changes = []} = Data) ->
     {next_state, State#state{state1 = idle}, Data};
handle_event(enter,
             _Old_state,
             #state{reliability_level = reliable,
                    state1 = announcing},
             #data{push_mode = true} = Data ->
     {next_state, State#state{state1 = pushing}, Data};
handle_event(enter,
             _Old_state,
             #state{reliability_level = reliable,
                    state1 = announcing},
             #data{push_mode = false,
                   heartbeat_period = Heartbeat_period} = Data) ->
     Hb_timer_ref = erlang:start_timer(Heartbeat_period, self(), hb),
     {keep_state, Data#data{heartbeat_timer_ref = Hb_timer_ref}};

Currently, I resort to testing for a particular condition in the state
enter code and insert a next event (changes_not_empty) to trigger the
state change I need, which however also introduces extra code and makes
things also less obvious.

Also, your co-locating argument is arguable since when using complex
states and handle_events, I tend to use a different ordering of the
functions, mostly focusing on the type of events to handle. (I know,
'enter' isn't an event of course.)

Frans


On 06/11/2018 10:45 AM, Raimo Niskanen wrote:

> On Mon, Jun 11, 2018 at 09:43:02AM +0200, Frans Schneider wrote:
>> Dear list,
>>
>> the state_enter_result(State) has as one its return values {next_state,
>> State, NewData, ...} but explicitly disallows the State to be different
>> from the current State,  First, I find it confusing to allow
>> 'next_state' here since State cannot change but the name makes the
>
> Throughout gen_statem is is by design so that there is no difference
> between {next_state, SameState, NewData, ...}
> and {keep_state, NewData, ...}.  It is {keep_state, ...} that is shorthand
> for {next_state, SameState, ...}.
>
> So dissalowing {next_state, ...} in a state_enter call would be an
> exception to the main rule that {next_state, ...} is the generic form.
>
> There might be situations where you have a helper function that calculates
> the next state so it would want to always return {next_state, ...}, and
> then you want to use it in a state_enter call context knowing it will
> always return {next_state, SameState, ...}.  I think this use should be
> possible.
>
>> suggestion it can. Secondly, I would love to be able to actually make a
>> state change. Quite often i find myself running a few tests on entering
>> a state and making state changes based on these tests. Now I have to
>
> The state_enter call is intended for code to be executed when entering a
> state, making this code co-located in the source with the state's event
> handling code.
>
> If the state_enter call should be allowed to change states it would no
> longer be co-located since it would for example start a timer for the other
> state it changes to.
>
> The alternative to using state_enter calls would be to have a convention
> where you at exit from the other state go through a helper function
> that does what you need for state entry, and remember to use this
> function for every state entry to the state in question.
>
> I think your use case is a good example for when having a specialized state
> change function makes sense.  There is no single state this kind of code
> clearly should be co-located with.
>
>> move these tests to all states which can have this state as a result and
>> go directly to the end state.
>>
>> Would it be possible to change state from a state enter call?
>
> I do not like that idea, for the reasons above...
>
>>
>> Frans
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
On Mon, Jun 11, 2018 at 01:07:31PM +0200, Frans Schneider wrote:

> Thanks Raimo,
>
> See your point, but I am not yet really convinced.
> Using a helper function would in my case.opinion  make the code more
> obliterative. I think the following code makes it very clear what goes
> on while some helper function called from whatever other place would
> make things much more obscure.
>
> handle_event(enter,
>     _Old_state,
>     #state{reliability_level = reliable,
>            state1 = announcing} = State,
>     #data{changes = []} = Data) ->
>      {next_state, State#state{state1 = idle}, Data};
> handle_event(enter,
>     _Old_state,
>     #state{reliability_level = reliable,
>            state1 = announcing},
>     #data{push_mode = true} = Data ->
>      {next_state, State#state{state1 = pushing}, Data};
> handle_event(enter,
>     _Old_state,
>     #state{reliability_level = reliable,
>            state1 = announcing},
>     #data{push_mode = false,
>           heartbeat_period = Heartbeat_period} = Data) ->
>      Hb_timer_ref = erlang:start_timer(Heartbeat_period, self(), hb),
>      {keep_state, Data#data{heartbeat_timer_ref = Hb_timer_ref}};

<unrelated>

Why not use a generic timeout here?
    {keep_state_and_data, [{{timeout,heartbeat},Heartbeat_period,hb}]};

</unrelated>

>
> Currently, I resort to testing for a particular condition in the state
> enter code and insert a next event (changes_not_empty) to trigger the
> state change I need, which however also introduces extra code and makes
> things also less obvious.
>
> Also, your co-locating argument is arguable since when using complex

Yes, I might be biased towards state_functions, but in that mode it is a
valid argument...  And allowing it would be against the philosophy of
that mode.  Increasing freedom is not always a good thing. ;-(
And allowing it for only one mode would be against the principle of
least surprise.


> states and handle_events, I tend to use a different ordering of the
> functions, mostly focusing on the type of events to handle. (I know,
> 'enter' isn't an event of course.)

The feature you ask for probably makes more sense for
handle_event_function.

But there are still some hairy semantics to get right:
* If changing states in state_enter call hops more than once - what should
  OldState be after the first state_enter call?  I.e how much of a state
  change should changing states in the state_enter call be regarded as.
  As the gen_statem code looks now it seems to be easier to retain
  the original OldState and not regard the state change as complete until
  the final state of the state change has been deduced.
* If changing states back to the OldState - should postponed events be
  retried?  I think not - you in effect stayed in the same state.
* Same again: if changing states back to the OldState - should the state
  enter call of the OldState be re-run or not?  As the gen_statem code
  looks now it seems to be hard to see this as anything else than staying
  in the same state hence not running the state_enter call.
  Is that intuitive?
* Should we also allow {next_event,...} from a state_enter call?
* There might be more...

I think the semantics is hairy enough already, so I still have a bad
feeling about this...  But I have asked some colleauages to also
contemplate it, we'll see if they can find the time since we are in
the middle of the 21.0 release circus.

The only thing that _has_ to be prohibited from a state_enter call is
'postpone' since there is no event to postpone.

The only reason I have for prohibiting state change and event insertion
from state_enter calls is that I am afraid they would get too hairy semantics
and hence cause too hard to understand state machines.

/ Raimo


>
> Frans
>
>
> On 06/11/2018 10:45 AM, Raimo Niskanen wrote:
> > On Mon, Jun 11, 2018 at 09:43:02AM +0200, Frans Schneider wrote:
> >> Dear list,
> >>
> >> the state_enter_result(State) has as one its return values {next_state,
> >> State, NewData, ...} but explicitly disallows the State to be different
> >> from the current State,  First, I find it confusing to allow
> >> 'next_state' here since State cannot change but the name makes the
> >
> > Throughout gen_statem is is by design so that there is no difference
> > between {next_state, SameState, NewData, ...}
> > and {keep_state, NewData, ...}.  It is {keep_state, ...} that is shorthand
> > for {next_state, SameState, ...}.
> >
> > So dissalowing {next_state, ...} in a state_enter call would be an
> > exception to the main rule that {next_state, ...} is the generic form.
> >
> > There might be situations where you have a helper function that calculates
> > the next state so it would want to always return {next_state, ...}, and
> > then you want to use it in a state_enter call context knowing it will
> > always return {next_state, SameState, ...}.  I think this use should be
> > possible.
> >
> >> suggestion it can. Secondly, I would love to be able to actually make a
> >> state change. Quite often i find myself running a few tests on entering
> >> a state and making state changes based on these tests. Now I have to
> >
> > The state_enter call is intended for code to be executed when entering a
> > state, making this code co-located in the source with the state's event
> > handling code.
> >
> > If the state_enter call should be allowed to change states it would no
> > longer be co-located since it would for example start a timer for the other
> > state it changes to.
> >
> > The alternative to using state_enter calls would be to have a convention
> > where you at exit from the other state go through a helper function
> > that does what you need for state entry, and remember to use this
> > function for every state entry to the state in question.
> >
> > I think your use case is a good example for when having a specialized state
> > change function makes sense.  There is no single state this kind of code
> > clearly should be co-located with.
> >
> >> move these tests to all states which can have this state as a result and
> >> go directly to the end state.
> >>
> >> Would it be possible to change state from a state enter call?
> >
> > I do not like that idea, for the reasons above...
> >
> >>
> >> Frans
> >
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Frans Schneider-2


On 06/11/2018 05:15 PM, Raimo Niskanen wrote:

> On Mon, Jun 11, 2018 at 01:07:31PM +0200, Frans Schneider wrote:
>> Thanks Raimo,
>>
>> See your point, but I am not yet really convinced.
>> Using a helper function would in my case.opinion  make the code more
>> obliterative. I think the following code makes it very clear what goes
>> on while some helper function called from whatever other place would
>> make things much more obscure.
>>
>> handle_event(enter,
>>     _Old_state,
>>     #state{reliability_level = reliable,
>>            state1 = announcing} = State,
>>     #data{changes = []} = Data) ->
>>       {next_state, State#state{state1 = idle}, Data};
>> handle_event(enter,
>>     _Old_state,
>>     #state{reliability_level = reliable,
>>            state1 = announcing},
>>     #data{push_mode = true} = Data ->
>>       {next_state, State#state{state1 = pushing}, Data};
>> handle_event(enter,
>>     _Old_state,
>>     #state{reliability_level = reliable,
>>            state1 = announcing},
>>     #data{push_mode = false,
>>           heartbeat_period = Heartbeat_period} = Data) ->
>>       Hb_timer_ref = erlang:start_timer(Heartbeat_period, self(), hb),
>>       {keep_state, Data#data{heartbeat_timer_ref = Hb_timer_ref}};
>
> <unrelated>
>
> Why not use a generic timeout here?
>      {keep_state_and_data, [{{timeout,heartbeat},Heartbeat_period,hb}]};
>
> </unrelated>

I have to be able to cancel the timer on entering the pushing state, so
I need the timer reference.

>
>>
>> Currently, I resort to testing for a particular condition in the state
>> enter code and insert a next event (changes_not_empty) to trigger the
>> state change I need, which however also introduces extra code and makes
>> things also less obvious.
>>
>> Also, your co-locating argument is arguable since when using complex
>
> Yes, I might be biased towards state_functions, but in that mode it is a
> valid argument...  And allowing it would be against the philosophy of
> that mode.  Increasing freedom is not always a good thing. ;-(
> And allowing it for only one mode would be against the principle of
> least surprise.
>
>
>> states and handle_events, I tend to use a different ordering of the
>> functions, mostly focusing on the type of events to handle. (I know,
>> 'enter' isn't an event of course.)
>
> The feature you ask for probably makes more sense for
> handle_event_function.
>
> But there are still some hairy semantics to get right:
> * If changing states in state_enter call hops more than once - what should
>    OldState be after the first state_enter call?  I.e how much of a state
>    change should changing states in the state_enter call be regarded as.
>    As the gen_statem code looks now it seems to be easier to retain
>    the original OldState and not regard the state change as complete until
>    the final state of the state change has been deduced.

Yes, I would expect a cascade of state enter calls with OldState
reflecting the states it goes through.

> * If changing states back to the OldState - should postponed events be
>    retried?  I think not - you in effect stayed in the same state.
> * Same again: if changing states back to the OldState - should the state
>    enter call of the OldState be re-run or not?  As the gen_statem code
>    looks now it seems to be hard to see this as anything else than staying
>    in the same state hence not running the state_enter call.
>    Is that intuitive?
> * Should we also allow {next_event,...} from a state_enter call?
> * There might be more...
>
> I think the semantics is hairy enough already, so I still have a bad
> feeling about this...  But I have asked some colleauages to also
> contemplate it, we'll see if they can find the time since we are in
> the middle of the 21.0 release circus.
>

Great! Should I send you a reminder sometime?

> The only thing that _has_ to be prohibited from a state_enter call is
> 'postpone' since there is no event to postpone.
>
> The only reason I have for prohibiting state change and event insertion
> from state_enter calls is that I am afraid they would get too hairy semantics
> and hence cause too hard to understand state machines.
>
> / Raimo
>
>
>>
>> Frans
>>
>>
>> On 06/11/2018 10:45 AM, Raimo Niskanen wrote:
>>> On Mon, Jun 11, 2018 at 09:43:02AM +0200, Frans Schneider wrote:
>>>> Dear list,
>>>>
>>>> the state_enter_result(State) has as one its return values {next_state,
>>>> State, NewData, ...} but explicitly disallows the State to be different
>>>> from the current State,  First, I find it confusing to allow
>>>> 'next_state' here since State cannot change but the name makes the
>>>
>>> Throughout gen_statem is is by design so that there is no difference
>>> between {next_state, SameState, NewData, ...}
>>> and {keep_state, NewData, ...}.  It is {keep_state, ...} that is shorthand
>>> for {next_state, SameState, ...}.
>>>
>>> So dissalowing {next_state, ...} in a state_enter call would be an
>>> exception to the main rule that {next_state, ...} is the generic form.
>>>
>>> There might be situations where you have a helper function that calculates
>>> the next state so it would want to always return {next_state, ...}, and
>>> then you want to use it in a state_enter call context knowing it will
>>> always return {next_state, SameState, ...}.  I think this use should be
>>> possible.
>>>
>>>> suggestion it can. Secondly, I would love to be able to actually make a
>>>> state change. Quite often i find myself running a few tests on entering
>>>> a state and making state changes based on these tests. Now I have to
>>>
>>> The state_enter call is intended for code to be executed when entering a
>>> state, making this code co-located in the source with the state's event
>>> handling code.
>>>
>>> If the state_enter call should be allowed to change states it would no
>>> longer be co-located since it would for example start a timer for the other
>>> state it changes to.
>>>
>>> The alternative to using state_enter calls would be to have a convention
>>> where you at exit from the other state go through a helper function
>>> that does what you need for state entry, and remember to use this
>>> function for every state entry to the state in question.
>>>
>>> I think your use case is a good example for when having a specialized state
>>> change function makes sense.  There is no single state this kind of code
>>> clearly should be co-located with.
>>>
>>>> move these tests to all states which can have this state as a result and
>>>> go directly to the end state.
>>>>
>>>> Would it be possible to change state from a state enter call?
>>>
>>> I do not like that idea, for the reasons above...
>>>
>>>>
>>>> Frans
>>>
>> _______________________________________________
>> erlang-questions mailing list
>> [hidden email]
>> http://erlang.org/mailman/listinfo/erlang-questions
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Vlad Dumitrescu-2
Hi!
On Mon, Jun 11, 2018 at 5:47 PM Frans Schneider <[hidden email]> wrote:


On 06/11/2018 05:15 PM, Raimo Niskanen wrote:
> But there are still some hairy semantics to get right:
> * If changing states in state_enter call hops more than once - what should
>    OldState be after the first state_enter call?  I.e how much of a state
>    change should changing states in the state_enter call be regarded as.
>    As the gen_statem code looks now it seems to be easier to retain
>    the original OldState and not regard the state change as complete until
>    the final state of the state change has been deduced.

Yes, I would expect a cascade of state enter calls with OldState
reflecting the states it goes through.

I am not a state machine expert, so maybe I am misunderstanding. If we allow jumping to arbitrary states from state_enter, isn't it like there is another state embedded there? 

One of the advantages of state machines is that it's easy to see from the code where the continuations are. One can no longer tell from examining the source state what states follow it, but one has to examine the state_enter of all these first.

Isn't it cleaner to just separate this complex state into several simpler ones? Or am I missing something?

best regards,
Vlad
 

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Fred Hebert-2
In reply to this post by Raimo Niskanen-2
On 06/11, Raimo Niskanen wrote:

>The feature you ask for probably makes more sense for
>handle_event_function.
>
>But there are still some hairy semantics to get right:
>* If changing states in state_enter call hops more than once - what should
>  OldState be after the first state_enter call?  I.e how much of a state
>  change should changing states in the state_enter call be regarded as.
>  As the gen_statem code looks now it seems to be easier to retain
>  the original OldState and not regard the state change as complete until
>  the final state of the state change has been deduced.
>* If changing states back to the OldState - should postponed events be
>  retried?  I think not - you in effect stayed in the same state.
>* Same again: if changing states back to the OldState - should the state
>  enter call of the OldState be re-run or not?  As the gen_statem code
>  looks now it seems to be hard to see this as anything else than staying
>  in the same state hence not running the state_enter call.
>  Is that intuitive?
>* Should we also allow {next_event,...} from a state_enter call?
>* There might be more...

- Would the state enter represent having entered the state or entering
  the state as soon as the callback returns?
  If it represents having entered the state:
    - postponing would conceptually make sense, even if it won't in
      practice (because you may just forever get back to the postponing
      state)
    - trace/debug calls need to show the state transition as having
      happened with no actual events being generated (or rather event a
      caused state to change to A, and then state changes B and C
      happened with no prompting) -- otherwise you need to consider
      state enter events the same way they'd be with external events and
      that's even conceptually worse
    - state timeouts are not ambiguous
    - if the events entered the state, it does solve the 'OldState'
      problem
  If it does not represent having entered the state
    - postponing would not conceptually make sense, and won't happen
    - no hard questions about whether enter state events are special
      events or not
    - state timeouts should possibly not be reset since you have never
      left the original state until at the end of evaluating the 'enter'
      chain of events
    - this ends up having the possibility to have a repeated 'OldState'
      value, which currently clashes with the expectation that the first
      enter event of a FSM is a duplicated one. This would make any
      double-transition look like the first transition of the process's
      loop.

It seems that either way, there is a conceptual confusion as soon as an
enter state event is allowed to force a change to a new state. I'd
rather see the possibility forbidden.

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
do the same thing as multiple enter-state events with a state transition
it seems.


>
>I think the semantics is hairy enough already, so I still have a bad
>feeling about this...  But I have asked some colleauages to also
>contemplate it, we'll see if they can find the time since we are in
>the middle of the 21.0 release circus.
>

Yes. One of the main complaints of gen_statem is that it is a complex
behaviour and people take a long while to get adapted. It could be
argued that the separation between enter and normal events is already
part of that complexity, but in my opinion, not putting these barriers
in place actually increase the overall potential system complexity even
more.

I still think gen_statem should have been simpler than it is today, so
I'll be vocal about preventing further complexity from being added to
it.

>The only thing that _has_ to be prohibited from a state_enter call is
>'postpone' since there is no event to postpone.
>
>The only reason I have for prohibiting state change and event insertion
>from state_enter calls is that I am afraid they would get too hairy semantics
>and hence cause too hard to understand state machines.
>

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.

Currently the typespecs are too obtuse to be good docs on their own
(while they're kind of okay for supervisors and gen_servers), but they
represent an intrinsinct complexity warning that a tutorial would have
to alert the user to anyway if they were to be simplified. Might as well
keep the type complexity and just have the tutorial fix things right
away.

I'm opposed to adding more conceptual complexity to gen_statem.

Regards,
Fred.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
In reply to this post by Frans Schneider-2
On Mon, Jun 11, 2018 at 05:47:30PM +0200, Frans Schneider wrote:

>
>
> On 06/11/2018 05:15 PM, Raimo Niskanen wrote:
> > On Mon, Jun 11, 2018 at 01:07:31PM +0200, Frans Schneider wrote:
> >> Thanks Raimo,
> >>
> >> See your point, but I am not yet really convinced.
> >> Using a helper function would in my case.opinion  make the code more
> >> obliterative. I think the following code makes it very clear what goes
> >> on while some helper function called from whatever other place would
> >> make things much more obscure.
> >>
> >> handle_event(enter,
> >>     _Old_state,
> >>     #state{reliability_level = reliable,
> >>            state1 = announcing} = State,
> >>     #data{changes = []} = Data) ->
> >>       {next_state, State#state{state1 = idle}, Data};
> >> handle_event(enter,
> >>     _Old_state,
> >>     #state{reliability_level = reliable,
> >>            state1 = announcing},
> >>     #data{push_mode = true} = Data ->
> >>       {next_state, State#state{state1 = pushing}, Data};
> >> handle_event(enter,
> >>     _Old_state,
> >>     #state{reliability_level = reliable,
> >>            state1 = announcing},
> >>     #data{push_mode = false,
> >>           heartbeat_period = Heartbeat_period} = Data) ->
> >>       Hb_timer_ref = erlang:start_timer(Heartbeat_period, self(), hb),
> >>       {keep_state, Data#data{heartbeat_timer_ref = Hb_timer_ref}};
> >
> > <unrelated>
> >
> > Why not use a generic timeout here?
> >      {keep_state_and_data, [{{timeout,heartbeat},Heartbeat_period,hb}]};
> >
> > </unrelated>
>
> I have to be able to cancel the timer on entering the pushing state, so
> I need the timer reference.

Cancel - set it to infinity:
  [{timeout,heartbeat},infinity,hb}]

>
> >
> >>
> >> Currently, I resort to testing for a particular condition in the state
> >> enter code and insert a next event (changes_not_empty) to trigger the
> >> state change I need, which however also introduces extra code and makes
> >> things also less obvious.
> >>
> >> Also, your co-locating argument is arguable since when using complex
> >
> > Yes, I might be biased towards state_functions, but in that mode it is a
> > valid argument...  And allowing it would be against the philosophy of
> > that mode.  Increasing freedom is not always a good thing. ;-(
> > And allowing it for only one mode would be against the principle of
> > least surprise.
> >
> >
> >> states and handle_events, I tend to use a different ordering of the
> >> functions, mostly focusing on the type of events to handle. (I know,
> >> 'enter' isn't an event of course.)
> >
> > The feature you ask for probably makes more sense for
> > handle_event_function.
> >
> > But there are still some hairy semantics to get right:
> > * If changing states in state_enter call hops more than once - what should
> >    OldState be after the first state_enter call?  I.e how much of a state
> >    change should changing states in the state_enter call be regarded as.
> >    As the gen_statem code looks now it seems to be easier to retain
> >    the original OldState and not regard the state change as complete until
> >    the final state of the state change has been deduced.
>
> Yes, I would expect a cascade of state enter calls with OldState
> reflecting the states it goes through.

...which would be the hard one to implement...

>
> > * If changing states back to the OldState - should postponed events be
> >    retried?  I think not - you in effect stayed in the same state.
> > * Same again: if changing states back to the OldState - should the state
> >    enter call of the OldState be re-run or not?  As the gen_statem code
> >    looks now it seems to be hard to see this as anything else than staying
> >    in the same state hence not running the state_enter call.
> >    Is that intuitive?
> > * Should we also allow {next_event,...} from a state_enter call?
> > * There might be more...
> >
> > I think the semantics is hairy enough already, so I still have a bad
> > feeling about this...  But I have asked some colleauages to also
> > contemplate it, we'll see if they can find the time since we are in
> > the middle of the 21.0 release circus.
> >
>
> Great! Should I send you a reminder sometime?

After the release of 21.0, if this discussion dies off now.


>
> > The only thing that _has_ to be prohibited from a state_enter call is
> > 'postpone' since there is no event to postpone.
> >
> > The only reason I have for prohibiting state change and event insertion
> > from state_enter calls is that I am afraid they would get too hairy semantics
> > and hence cause too hard to understand state machines.
> >
> > / Raimo
> >
> >
> >>
> >> Frans
> >>
> >>
> >> On 06/11/2018 10:45 AM, Raimo Niskanen wrote:
> >>> On Mon, Jun 11, 2018 at 09:43:02AM +0200, Frans Schneider wrote:
> >>>> Dear list,
> >>>>
> >>>> the state_enter_result(State) has as one its return values {next_state,
> >>>> State, NewData, ...} but explicitly disallows the State to be different
> >>>> from the current State,  First, I find it confusing to allow
> >>>> 'next_state' here since State cannot change but the name makes the
> >>>
> >>> Throughout gen_statem is is by design so that there is no difference
> >>> between {next_state, SameState, NewData, ...}
> >>> and {keep_state, NewData, ...}.  It is {keep_state, ...} that is shorthand
> >>> for {next_state, SameState, ...}.
> >>>
> >>> So dissalowing {next_state, ...} in a state_enter call would be an
> >>> exception to the main rule that {next_state, ...} is the generic form.
> >>>
> >>> There might be situations where you have a helper function that calculates
> >>> the next state so it would want to always return {next_state, ...}, and
> >>> then you want to use it in a state_enter call context knowing it will
> >>> always return {next_state, SameState, ...}.  I think this use should be
> >>> possible.
> >>>
> >>>> suggestion it can. Secondly, I would love to be able to actually make a
> >>>> state change. Quite often i find myself running a few tests on entering
> >>>> a state and making state changes based on these tests. Now I have to
> >>>
> >>> The state_enter call is intended for code to be executed when entering a
> >>> state, making this code co-located in the source with the state's event
> >>> handling code.
> >>>
> >>> If the state_enter call should be allowed to change states it would no
> >>> longer be co-located since it would for example start a timer for the other
> >>> state it changes to.
> >>>
> >>> The alternative to using state_enter calls would be to have a convention
> >>> where you at exit from the other state go through a helper function
> >>> that does what you need for state entry, and remember to use this
> >>> function for every state entry to the state in question.
> >>>
> >>> I think your use case is a good example for when having a specialized state
> >>> change function makes sense.  There is no single state this kind of code
> >>> clearly should be co-located with.
> >>>
> >>>> move these tests to all states which can have this state as a result and
> >>>> go directly to the end state.
> >>>>
> >>>> Would it be possible to change state from a state enter call?
> >>>
> >>> I do not like that idea, for the reasons above...
> >>>
> >>>>
> >>>> Frans
> >>>
> >> _______________________________________________
> >> erlang-questions mailing list
> >> [hidden email]
> >> http://erlang.org/mailman/listinfo/erlang-questions
> >
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
In reply to this post by Fred Hebert-2
On Mon, Jun 11, 2018 at 12:55:40PM -0400, Fred Hebert wrote:

> On 06/11, Raimo Niskanen wrote:
> >The feature you ask for probably makes more sense for
> >handle_event_function.
> >
> >But there are still some hairy semantics to get right:
> >* If changing states in state_enter call hops more than once - what should
> >  OldState be after the first state_enter call?  I.e how much of a state
> >  change should changing states in the state_enter call be regarded as.
> >  As the gen_statem code looks now it seems to be easier to retain
> >  the original OldState and not regard the state change as complete until
> >  the final state of the state change has been deduced.
> >* If changing states back to the OldState - should postponed events be
> >  retried?  I think not - you in effect stayed in the same state.
> >* Same again: if changing states back to the OldState - should the state
> >  enter call of the OldState be re-run or not?  As the gen_statem code
> >  looks now it seems to be hard to see this as anything else than staying
> >  in the same state hence not running the state_enter call.
> >  Is that intuitive?
> >* Should we also allow {next_event,...} from a state_enter call?
> >* There might be more...
>
> - Would the state enter represent having entered the state or entering
>   the state as soon as the callback returns?
>   If it represents having entered the state:
>     - postponing would conceptually make sense, even if it won't in
>       practice (because you may just forever get back to the postponing
>       state)
>     - trace/debug calls need to show the state transition as having
>       happened with no actual events being generated (or rather event a
>       caused state to change to A, and then state changes B and C
>       happened with no prompting) -- otherwise you need to consider
>       state enter events the same way they'd be with external events and
>       that's even conceptually worse
>     - state timeouts are not ambiguous
>     - if the events entered the state, it does solve the 'OldState'
>       problem
>   If it does not represent having entered the state
>     - postponing would not conceptually make sense, and won't happen
>     - no hard questions about whether enter state events are special
>       events or not
>     - state timeouts should possibly not be reset since you have never
>       left the original state until at the end of evaluating the 'enter'
>       chain of events
>     - this ends up having the possibility to have a repeated 'OldState'
>       value, which currently clashes with the expectation that the first
>       enter event of a FSM is a duplicated one. This would make any
>       double-transition look like the first transition of the process's
>       loop.

Sorry - I do not follow your argument about postponing making sense for
"state entered" but not for "entering state"...

I once contemplated having 'enter' as a pure automatically inserted event
then possible to postpone, insert, and so on, but realized the hairiness
that state machines utilizing that would achieve.  So I went with 'enter'
is not an event - it is a special call, so I will have no discussion
about postponing it!

Therefore I want it to be called "state enter call", not anything
implicating "event"...

>
> It seems that either way, there is a conceptual confusion as soon as an
> enter state event is allowed to force a change to a new state. I'd
> rather see the possibility forbidden.
>
> 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...

> do the same thing as multiple enter-state events with a state transition
> it seems.

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 think the semantics is hairy enough already, so I still have a bad
> >feeling about this...  But I have asked some colleauages to also
> >contemplate it, we'll see if they can find the time since we are in
> >the middle of the 21.0 release circus.
> >
>
> Yes. One of the main complaints of gen_statem is that it is a complex
> behaviour and people take a long while to get adapted. It could be
> argued that the separation between enter and normal events is already
> part of that complexity, but in my opinion, not putting these barriers
> in place actually increase the overall potential system complexity even
> more.

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.

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 still think gen_statem should have been simpler than it is today, so
> I'll be vocal about preventing further complexity from being added to
> it.
>
> >The only thing that _has_ to be prohibited from a state_enter call is
> >'postpone' since there is no event to postpone.
> >
> >The only reason I have for prohibiting state change and event insertion
> >from state_enter calls is that I am afraid they would get too hairy semantics
> >and hence cause too hard to understand state machines.
> >
>
> 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?

>
> Currently the typespecs are too obtuse to be good docs on their own
> (while they're kind of okay for supervisors and gen_servers), but they
> represent an intrinsinct complexity warning that a tutorial would have
> to alert the user to anyway if they were to be simplified. Might as well
> keep the type complexity and just have the tutorial fix things right
> away.

I guess you are talking about the Reference Manual that is more or less
built around the type specs, in contrast to the User's Guide (gen_statem
Behaviour decription).

From 21.0-rc2:
    http://erlang.org/documentation/doc-10.0-rc2/lib/stdlib-3.5/doc/html/gen_statem.html
    http://erlang.org/documentation/doc-10.0-rc2/doc/design_principles/statem.html

Improvement suggestions on both are welcome!


>
> I'm opposed to adding more conceptual complexity to gen_statem.

I think we are on the same side this time. :-)

>
> Regards,
> Fred.

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Frans Schneider-2


On 06/12/2018 10:06 AM, Raimo Niskanen wrote:

>
> 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.
>
This is the exact reason for bringing up the whole issue. And sometimes,
that requires a next_state NewState.
>

Frans
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Ingela Andin
Hi!

Could it be so that you are trying to solve the problem in the wrong place? State enter actions are good for example starting a timer, but I think it is illogical to change the state.
It sound to me like you could do so pattern matching, maybe in conjunction with some guards in the your state function clauses and if your conditions are meet maybe
all that clause does is to change state.

Regards Ingela Erlang/OTP team - Ericsson AB



2018-06-12 10:31 GMT+02:00 Frans Schneider <[hidden email]>:


On 06/12/2018 10:06 AM, Raimo Niskanen wrote:


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.

This is the exact reason for bringing up the whole issue. And sometimes, that requires a next_state NewState.


Frans

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Frans Schneider-2


On 06/12/2018 12:31 PM, Ingela Andin wrote:

> Hi!
>
> Could it be so that you are trying to solve the problem in the wrong
> place? State enter actions are good for example starting a timer, but I
> think it is illogical to change the state.
> It sound to me like you could do so pattern matching, maybe in
> conjunction with some guards in the your state function clauses and if
> your conditions are meet maybe
> all that clause does is to change state.
>
> Regards Ingela Erlang/OTP team - Ericsson AB
>
>

Of course I can, but in this particular case it would lead to simpler
code. For example, I have the following state changes defined ([1] par.
8.4.12.2):

__|_____________|_____________________________|____________________
T2|waiting      |HEARTBEAT message is received|if (HB.FinalFlag ==
   |             |                             |    NOT_SET) then
   |             |                             |  must_send_ack else if
   |             |                             |   (HB.LivelinessFlag ==
   |             |                             |   NOT_SET) then
   |             |                             |  may_send_ack
   |             |                             |else
   |             |                             |  waiting
__|_____________|_____________________________|____________________
T3|may_send_ack |GuardCondition:              |waiting
   |             |   WP::missing_changes() ==  |
   |             |       <empty>               |
__|_____________|_____________________________|____________________
T4|may_send_ack |GuardCondition:              |must_send_ack
   |             |   WP::missing_changes() !=  |
   |             |       <empty>               |
__|_____________|_____________________________|____________________
T5|must_send_ack|after(R::heartbeatResponseDelay) waiting
__|_____________|_____________________________|____________________

Transitions T3/T4 are just begging for a enter state with next_state.
The alternative would be to put the guard conditions at the end of T2,
which of course is not that bad but it messes up the line of thinking of
the original protocol designers and its readers. Also, T3/T4 could just
become something like:

handle_event(enter,
             _Old_state,
             #state{state2 = may_send_ack} = State,
             #data{missing_changes = []} = Data) ->
     {next_state, waiting, Data};
handle_event(enter,
             _Old_state,
             #state{state2 = may_send_ack} = State,
             Data) ->
     ...
     {next_state, must_send_ack, Data};

which I think is very accurate. In the example, there is only T2 as the
old state but there are other examples with more than one originating
states.

Frans

[1] https://www.omg.org/spec/DDSI-RTPS/2.2/PDF

>
> 2018-06-12 10:31 GMT+02:00 Frans Schneider <[hidden email]
> <mailto:[hidden email]>>:
>
>
>
>     On 06/12/2018 10:06 AM, Raimo Niskanen wrote:
>
>
>         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.
>
>     This is the exact reason for bringing up the whole issue. And
>     sometimes, that requires a next_state NewState.
>
>
>
>     Frans
>
>     _______________________________________________
>     erlang-questions mailing list
>     [hidden email] <mailto:[hidden email]>
>     http://erlang.org/mailman/listinfo/erlang-questions
>     <http://erlang.org/mailman/listinfo/erlang-questions>
>
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Ingela Andin
Hi!

Then I think it sounds like you could have a help function to call when you leave the previous states to determine what should be the next state so that you make the "TM" right transition in the first place.
Or what am I not understanding?

Regards Ingela Erlang/OTP team Ericsson AB


2018-06-12 14:14 GMT+02:00 Frans Schneider <[hidden email]>:


On 06/12/2018 12:31 PM, Ingela Andin wrote:
Hi!

Could it be so that you are trying to solve the problem in the wrong place? State enter actions are good for example starting a timer, but I think it is illogical to change the state.
It sound to me like you could do so pattern matching, maybe in conjunction with some guards in the your state function clauses and if your conditions are meet maybe
all that clause does is to change state.

Regards Ingela Erlang/OTP team - Ericsson AB



Of course I can, but in this particular case it would lead to simpler code. For example, I have the following state changes defined ([1] par. 8.4.12.2):

__|_____________|_____________________________|____________________
T2|waiting      |HEARTBEAT message is received|if (HB.FinalFlag ==
  |             |                             |    NOT_SET) then
  |             |                             |  must_send_ack else if
  |             |                             |   (HB.LivelinessFlag ==
  |             |                             |   NOT_SET) then
  |             |                             |  may_send_ack
  |             |                             |else
  |             |                             |  waiting
__|_____________|_____________________________|____________________
T3|may_send_ack |GuardCondition:              |waiting
  |             |   WP::missing_changes() ==  |
  |             |       <empty>               |
__|_____________|_____________________________|____________________
T4|may_send_ack |GuardCondition:              |must_send_ack
  |             |   WP::missing_changes() !=  |
  |             |       <empty>               |
__|_____________|_____________________________|____________________
T5|must_send_ack|after(R::heartbeatResponseDelay) waiting
__|_____________|_____________________________|____________________

Transitions T3/T4 are just begging for a enter state with next_state. The alternative would be to put the guard conditions at the end of T2, which of course is not that bad but it messes up the line of thinking of the original protocol designers and its readers. Also, T3/T4 could just become something like:

handle_event(enter,
             _Old_state,
             #state{state2 = may_send_ack} = State,
             #data{missing_changes = []} = Data) ->
    {next_state, waiting, Data};
handle_event(enter,
             _Old_state,
             #state{state2 = may_send_ack} = State,
             Data) ->
    ...
    {next_state, must_send_ack, Data};

which I think is very accurate. In the example, there is only T2 as the old state but there are other examples with more than one originating states.

Frans

[1] https://www.omg.org/spec/DDSI-RTPS/2.2/PDF


2018-06-12 10:31 GMT+02:00 Frans Schneider <[hidden email] <mailto:[hidden email]>>:



    On 06/12/2018 10:06 AM, Raimo Niskanen wrote:


        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.

    This is the exact reason for bringing up the whole issue. And
    sometimes, that requires a next_state NewState.



    Frans

    _______________________________________________
    erlang-questions mailing list
    [hidden email] <mailto:[hidden email]>
    http://erlang.org/mailman/listinfo/erlang-questions
    <http://erlang.org/mailman/listinfo/erlang-questions>




_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
In reply to this post by Frans Schneider-2
On Tue, Jun 12, 2018 at 02:14:45PM +0200, Frans Schneider wrote:

>
>
> On 06/12/2018 12:31 PM, Ingela Andin wrote:
> > Hi!
> >
> > Could it be so that you are trying to solve the problem in the wrong
> > place? State enter actions are good for example starting a timer, but I
> > think it is illogical to change the state.
> > It sound to me like you could do so pattern matching, maybe in
> > conjunction with some guards in the your state function clauses and if
> > your conditions are meet maybe
> > all that clause does is to change state.
> >
> > Regards Ingela Erlang/OTP team - Ericsson AB
> >
> >
>
> Of course I can, but in this particular case it would lead to simpler
> code. For example, I have the following state changes defined ([1] par.
> 8.4.12.2):
>
>  __|_____________|_____________________________|____________________
>  T2|waiting      |HEARTBEAT message is received|if (HB.FinalFlag ==
>    |             |                             |    NOT_SET) then
>    |             |                             |  must_send_ack else if
>    |             |                             |   (HB.LivelinessFlag ==
>    |             |                             |   NOT_SET) then
>    |             |                             |  may_send_ack
>    |             |                             |else
>    |             |                             |  waiting
>  __|_____________|_____________________________|____________________
>  T3|may_send_ack |GuardCondition:              |waiting
>    |             |   WP::missing_changes() ==  |
>    |             |       <empty>               |
>  __|_____________|_____________________________|____________________
>  T4|may_send_ack |GuardCondition:              |must_send_ack
>    |             |   WP::missing_changes() !=  |
>    |             |       <empty>               |
>  __|_____________|_____________________________|____________________
>  T5|must_send_ack|after(R::heartbeatResponseDelay) waiting
>  __|_____________|_____________________________|____________________
>
> Transitions T3/T4 are just begging for a enter state with next_state.
> The alternative would be to put the guard conditions at the end of T2,
> which of course is not that bad but it messes up the line of thinking of
> the original protocol designers and its readers. Also, T3/T4 could just
> become something like:
>
> handle_event(enter,
>     _Old_state,
>     #state{state2 = may_send_ack} = State,
>     #data{missing_changes = []} = Data) ->
>      {next_state, waiting, Data};
> handle_event(enter,
>     _Old_state,
>     #state{state2 = may_send_ack} = State,
>     Data) ->
>      ...
>      {next_state, must_send_ack, Data};
>
> which I think is very accurate. In the example, there is only T2 as the
> old state but there are other examples with more than one originating
> states.
>
> Frans
>
> [1] https://www.omg.org/spec/DDSI-RTPS/2.2/PDF

Your example illustrates the problem.

But I think the specification in question is very strange, and an ill fit
for an event driven state machine.

I can see that "HEARTBEAT message is received" is an event, but think it is
very strange that [WP::missing_changes() == <empty>] is an event - it seems
more like a condition on the state, and hence the state may_send_ack appears
to not be a state but instead some kind of decision point.

So the state chart "8.24 - Behavior of the Reliable StatefulReader with
respect to each matched Writer" seems to be a mix of a state diagram and a
flowchart.

Also, specifying a state machine as a table of transitions is a bit odd, but
gets even more peculiar when the events are e.g "RTPS Reader is configured with
a matched RTPS Writer." or "GuardCondition: WP::missing_changes() == <empty>"
- here again a mix between a state transition table and flowchart conditions.

Since the specification in question seems to be quite far from an event driven
state machine it is no wonder if it seems to fit gen_statem poorly... :-(

Implementing flowchart logic via pseudo state changes calling back and
forth between the behaviour engine and the callback module is strange and
inefficient, but in this case has the advantage of making the
implementation kind of match the specification.

So the question boils down to; would allowing state change from state enter
calls be bad for what gen_statem is intended to be good at?

/ Raimo



>
> >
> > 2018-06-12 10:31 GMT+02:00 Frans Schneider <[hidden email]
> > <mailto:[hidden email]>>:
> >
> >
> >
> >     On 06/12/2018 10:06 AM, Raimo Niskanen wrote:
> >
> >
> >         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.
> >
> >     This is the exact reason for bringing up the whole issue. And
> >     sometimes, that requires a next_state NewState.
> >
> >
> >
> >     Frans
> >

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Frans Schneider-2
Indeed, the specs are a little weird. It's for a reason the organization
calls itself OMG.

Frans

On 06/12/2018 03:47 PM, Raimo Niskanen wrote:

> On Tue, Jun 12, 2018 at 02:14:45PM +0200, Frans Schneider wrote:
>>
>>
>> On 06/12/2018 12:31 PM, Ingela Andin wrote:
>>> Hi!
>>>
>>> Could it be so that you are trying to solve the problem in the wrong
>>> place? State enter actions are good for example starting a timer, but I
>>> think it is illogical to change the state.
>>> It sound to me like you could do so pattern matching, maybe in
>>> conjunction with some guards in the your state function clauses and if
>>> your conditions are meet maybe
>>> all that clause does is to change state.
>>>
>>> Regards Ingela Erlang/OTP team - Ericsson AB
>>>
>>>
>>
>> Of course I can, but in this particular case it would lead to simpler
>> code. For example, I have the following state changes defined ([1] par.
>> 8.4.12.2):
>>
>>   __|_____________|_____________________________|____________________
>>   T2|waiting      |HEARTBEAT message is received|if (HB.FinalFlag ==
>>     |             |                             |    NOT_SET) then
>>     |             |                             |  must_send_ack else if
>>     |             |                             |   (HB.LivelinessFlag ==
>>     |             |                             |   NOT_SET) then
>>     |             |                             |  may_send_ack
>>     |             |                             |else
>>     |             |                             |  waiting
>>   __|_____________|_____________________________|____________________
>>   T3|may_send_ack |GuardCondition:              |waiting
>>     |             |   WP::missing_changes() ==  |
>>     |             |       <empty>               |
>>   __|_____________|_____________________________|____________________
>>   T4|may_send_ack |GuardCondition:              |must_send_ack
>>     |             |   WP::missing_changes() !=  |
>>     |             |       <empty>               |
>>   __|_____________|_____________________________|____________________
>>   T5|must_send_ack|after(R::heartbeatResponseDelay) waiting
>>   __|_____________|_____________________________|____________________
>>
>> Transitions T3/T4 are just begging for a enter state with next_state.
>> The alternative would be to put the guard conditions at the end of T2,
>> which of course is not that bad but it messes up the line of thinking of
>> the original protocol designers and its readers. Also, T3/T4 could just
>> become something like:
>>
>> handle_event(enter,
>>     _Old_state,
>>     #state{state2 = may_send_ack} = State,
>>     #data{missing_changes = []} = Data) ->
>>       {next_state, waiting, Data};
>> handle_event(enter,
>>     _Old_state,
>>     #state{state2 = may_send_ack} = State,
>>     Data) ->
>>       ...
>>       {next_state, must_send_ack, Data};
>>
>> which I think is very accurate. In the example, there is only T2 as the
>> old state but there are other examples with more than one originating
>> states.
>>
>> Frans
>>
>> [1] https://www.omg.org/spec/DDSI-RTPS/2.2/PDF
>
> Your example illustrates the problem.
>
> But I think the specification in question is very strange, and an ill fit
> for an event driven state machine.
>
> I can see that "HEARTBEAT message is received" is an event, but think it is
> very strange that [WP::missing_changes() == <empty>] is an event - it seems
> more like a condition on the state, and hence the state may_send_ack appears
> to not be a state but instead some kind of decision point.
>
> So the state chart "8.24 - Behavior of the Reliable StatefulReader with
> respect to each matched Writer" seems to be a mix of a state diagram and a
> flowchart.
>
> Also, specifying a state machine as a table of transitions is a bit odd, but
> gets even more peculiar when the events are e.g "RTPS Reader is configured with
> a matched RTPS Writer." or "GuardCondition: WP::missing_changes() == <empty>"
> - here again a mix between a state transition table and flowchart conditions.
>
> Since the specification in question seems to be quite far from an event driven
> state machine it is no wonder if it seems to fit gen_statem poorly... :-(
>
> Implementing flowchart logic via pseudo state changes calling back and
> forth between the behaviour engine and the callback module is strange and
> inefficient, but in this case has the advantage of making the
> implementation kind of match the specification.
>
> So the question boils down to; would allowing state change from state enter
> calls be bad for what gen_statem is intended to be good at?
>
> / Raimo
>
>
>
>>
>>>
>>> 2018-06-12 10:31 GMT+02:00 Frans Schneider <[hidden email]
>>> <mailto:[hidden email]>>:
>>>
>>>
>>>
>>>      On 06/12/2018 10:06 AM, Raimo Niskanen wrote:
>>>
>>>
>>>          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.
>>>
>>>      This is the exact reason for bringing up the whole issue. And
>>>      sometimes, that requires a next_state NewState.
>>>
>>>
>>>
>>>      Frans
>>>
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Fred Hebert-2
In reply to this post by Raimo Niskanen-2
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.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Fred Hebert-2
In reply to this post by Frans Schneider-2
On 06/12, Frans Schneider wrote:

>
>__|_____________|_____________________________|____________________
>T2|waiting      |HEARTBEAT message is received|if (HB.FinalFlag ==
>  |             |                             |    NOT_SET) then
>  |             |                             |  must_send_ack else if
>  |             |                             |   (HB.LivelinessFlag ==
>  |             |                             |   NOT_SET) then
>  |             |                             |  may_send_ack
>  |             |                             |else
>  |             |                             |  waiting
>__|_____________|_____________________________|____________________
>T3|may_send_ack |GuardCondition:              |waiting
>  |             |   WP::missing_changes() ==  |
>  |             |       <empty>               |
>__|_____________|_____________________________|____________________
>T4|may_send_ack |GuardCondition:              |must_send_ack
>  |             |   WP::missing_changes() !=  |
>  |             |       <empty>               |
>__|_____________|_____________________________|____________________
>T5|must_send_ack|after(R::heartbeatResponseDelay) waiting
>__|_____________|_____________________________|____________________
>
>Transitions T3/T4 are just begging for a enter state with next_state.
>The alternative would be to put the guard conditions at the end of T2,
>which of course is not that bad but it messes up the line of thinking
>of the original protocol designers and its readers. Also, T3/T4 could
>just become something like:
>
>handle_event(enter,
>     _Old_state,
>     #state{state2 = may_send_ack} = State,
>     #data{missing_changes = []} = Data) ->
>    {next_state, waiting, Data};
>handle_event(enter,
>     _Old_state,
>     #state{state2 = may_send_ack} = State,
>     Data) ->
>    ...
>    {next_state, must_send_ack, Data};
>
>which I think is very accurate. In the example, there is only T2 as
>the old state but there are other examples with more than one
>originating states.
>

Have you considered:

handle_event(..., ..., t2, Data) ->
    ...
    {next_state, may_send_ack, NewData,
     [{next_event, internal, switch_states}]};
handle_event(internal, switch_states, maybe_send_ack, Data) ->
    case Data of
        #data{missing_changes=[]} ->
            {next_state, waiting, Data};
        _ ->
            {next_state, must_send_ack, Data}
    end;
...


Using next_event to force-enqueue the next event lets you do a switch
through a state transition based entirely on its internal state. It does
require any transition to 'may_send_ack' to set that value, though.

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
In reply to this post by Fred Hebert-2
Top posting, for once, since I totally agree!
/ Raimo


On Tue, Jun 12, 2018 at 08:55:58PM -0400, Fred Hebert wrote:

> 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.

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Raimo Niskanen-2
In reply to this post by Frans Schneider-2
On Tue, Jun 12, 2018 at 04:37:51PM +0200, Frans Schneider wrote:
> Indeed, the specs are a little weird. It's for a reason the organization
> calls itself OMG.

What was it called?

ROFLMAO!

>
> Frans
>

--

/ Raimo Niskanen, Erlang/OTP, Ericsson AB
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: gen_statem state enter call and changing t oanother state

Frans Schneider-2
In reply to this post by Fred Hebert-2
Well, that was an enlightening discussion. Just ditched the initial
implementation and started all over again. Conditions now are true
conditions and events are real events. The number of states now is half
the number they were, but the handle_event functions tend to be more
complex. I could remove the whole state enter functionality.

Thanks,

Frans

On 06/13/2018 03:03 AM, Fred Hebert wrote:

> On 06/12, Frans Schneider wrote:
>>
>> __|_____________|_____________________________|____________________
>> T2|waiting      |HEARTBEAT message is received|if (HB.FinalFlag ==
>>  |             |                             |    NOT_SET) then
>>  |             |                             |  must_send_ack else if
>>  |             |                             |   (HB.LivelinessFlag ==
>>  |             |                             |   NOT_SET) then
>>  |             |                             |  may_send_ack
>>  |             |                             |else
>>  |             |                             |  waiting
>> __|_____________|_____________________________|____________________
>> T3|may_send_ack |GuardCondition:              |waiting
>>  |             |   WP::missing_changes() ==  |
>>  |             |       <empty>               |
>> __|_____________|_____________________________|____________________
>> T4|may_send_ack |GuardCondition:              |must_send_ack
>>  |             |   WP::missing_changes() !=  |
>>  |             |       <empty>               |
>> __|_____________|_____________________________|____________________
>> T5|must_send_ack|after(R::heartbeatResponseDelay) waiting
>> __|_____________|_____________________________|____________________
>>
>> Transitions T3/T4 are just begging for a enter state with next_state.
>> The alternative would be to put the guard conditions at the end of T2,
>> which of course is not that bad but it messes up the line of thinking
>> of the original protocol designers and its readers. Also, T3/T4 could
>> just become something like:
>>
>> handle_event(enter,
>>          _Old_state,
>>          #state{state2 = may_send_ack} = State,
>>          #data{missing_changes = []} = Data) ->
>>    {next_state, waiting, Data};
>> handle_event(enter,
>>          _Old_state,
>>          #state{state2 = may_send_ack} = State,
>>          Data) ->
>>    ...
>>    {next_state, must_send_ack, Data};
>>
>> which I think is very accurate. In the example, there is only T2 as
>> the old state but there are other examples with more than one
>> originating states.
>>
>
> Have you considered:
>
> handle_event(..., ..., t2, Data) ->
>     ...
>     {next_state, may_send_ack, NewData,
>      [{next_event, internal, switch_states}]};
> handle_event(internal, switch_states, maybe_send_ack, Data) ->
>     case Data of
>         #data{missing_changes=[]} ->
>             {next_state, waiting, Data};
>         _ ->
>             {next_state, must_send_ack, Data}
>     end;
> ...
>
>
> Using next_event to force-enqueue the next event lets you do a switch
> through a state transition based entirely on its internal state. It does
> require any transition to 'may_send_ack' to set that value, though.
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
12