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

Raimo Niskanen raimo+erlang-questions@REDACTED
Tue Jun 12 08:29:42 CEST 2018


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