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

Raimo Niskanen raimo+erlang-questions@REDACTED
Mon Jun 11 17:15:43 CEST 2018

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}};


Why not use a generic timeout here?
    {keep_state_and_data, [{{timeout,heartbeat},Heartbeat_period,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

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
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions


/ Raimo Niskanen, Erlang/OTP, Ericsson AB

More information about the erlang-questions mailing list