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

Frans Schneider fchschneider@REDACTED
Mon Jun 11 17:47:30 CEST 2018

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

More information about the erlang-questions mailing list