[erlang-questions] gen_statem and multiple timeout vs 1

Fred Hebert mononcqc@REDACTED
Wed Oct 5 05:52:38 CEST 2016


On 10/04, Vans S wrote:
>moving, attacking, jumping, stopped, moving_attacking, 
>moving_attacking_jumping, attacking_jumping, moving_jumping, 
>jumping_stopped, .. etc
>
>From what your saying, the state machine needs to be in ONLY ONE of these states at a time.
>

Well not necessarily, gen_statem now lets you use complex terms as 
state. So the state could be [moving, attacking, jumping], though not 
quite sure how you'd deal with these overlapped states. But it's 
possible to use a more abstract data structure than an atom to represent 
states now.

I.e. a timeout for running being triggered could transition you into a 
thing like:

handle_event({timeout, Ref, running}, ComplexState, Data) ->
    ...
    NextState = ComplexState -- [running],
    ...

or whatever format you'd like. This is new stuff and capacities of 
gen_statem that was not possible in gen_fsm, so best practices haven't 
been fully codified for this yet, but this should certainly be possible.

>
>This is true.  But then there needs to be a way to get the timer ref. 
> Sometimes Erlangs approach is, just make it work.  
>I don't see any way to get the timer ref without including it in the callback OR creating your own timer and including it for tracking (vs just specifying the timeout).
>

Yeah, I'm advocating for creating your own timer, where you do get a ref 
for it.


>I did not consider this. This is truly problematic BUT would not the 
>current way timeout works run into this same problem? So this should 
>not affect allowing multiple timeouts vs a single timeout.

Right now what you can do if you manage your own timers is to cancel the 
old ones or ignore them (you've got their refs so it's easy) and you can 
set new ones right away. You can't do that however if you rely on the 
return tuple of a state transition to do it, since that transition 
cannot happen from a code_change callback.

>Learning from that, I rather this be inside gen_statem. If its not, I 
>would have no problem to write my own little timer library for 
>cancel_timer+purging mailbox. 
>

Usually the pattern I use is to write a short 'reset_<whatever>_timer' 
function that returns its ref, and then I can track the ref in my state.

I always found this fairly convenient to deal with things, and for 
example, I mostly don't clean up timers very hard. I.e. you can make use 
of the newer cancel_timer options for async returns with or without 
info, and by matching on specific refs, I can just ignore timers that 
are for events I know are no longer up for consideration and let them 
disappear, and automatically can ignore a bunch of potential race 
conditions (i.e. no mailbox cleanup, just ignore messages coming in)

This is also neat to avoid whatever blocking or synchronization that 
could take place on timer management, but in some cases you may still 
want to do that.


>Since you are only in ONE state at a time, you automatically know if 
>the timeout happened, it MUST correspond to the current state we are 
>in. 
>

Does it? Couldn't the timeout event have been postponed, and therefore 
come from a prior state change? I believe so. There's no relationship 
between an event being handled in a state and that event having been 
sent in that state.

>Another option is to remove the timeout then. It just seems out of place to me.  
>

At the two extremes are the positions that all timeout management is 
manual, and that gen_statem is to replace full-on control of timeouts 
people can do manually (with all the cancellation options and whatnot).

Those are the two extremes, and the current thing is where on the 
gradient does the timeout implementation currently lie. So far it 
appears to be a fairly minimal convenience factor. I'm just wondering if 
it's worth pushing it further on the gradient of "emulating all the 
flexibility of manual control."

>What is the use case of a single timeout with a UNIQUE EventContent?
>

The current semantics are specific about one thing:

    Generates an event of event_type() timeout after this time (in 
    milliseconds) unless another event arrives in which case this 
    time-out is cancelled. Notice that a retried or inserted event 
    counts like a new in this respect.

These timeouts are mostly used for one thing in my experience: tracking 
inactivity of the state machine. gen_servers and gen_fsms have the same 
one. The only pragmatic use case I've made of them were to give myself a 
delay of N milliseconds before which it would be reasonable to send the 
process in hibernation, GCing and compacting its memory at once, since 
it had not received a message in that time otherwise.

All other timeouts I tend to manage by hand because I do not want them 
to be interruptible by the arrival of other messages in the mailbox of 
the process.

This is a very, very important detail! Those semantics are also a lot 
trickier to handle by yourself (you'd need to put them in every clause) 
than most other timers you could imagine using. This makes them a fairly 
good idea to include in the OTP behaviours themselves, as opposed to 
most other timer usages, IMO.

Regards,
Fred.



More information about the erlang-questions mailing list