[erlang-questions] gen_statem and multiple timeout vs 1

Raimo Niskanen raimo+erlang-questions@REDACTED
Mon Oct 3 11:45:15 CEST 2016


On Fri, Sep 30, 2016 at 02:25:13PM +0000, Vans S wrote:
> The reasoning behind this is because I am using a handle_event_function callback mode. I find this is more flexible.  A standard state machine can be in 1 state at 1 time.
> 
> But things are more complex now and the desire for a state machine to be in multiple states at 1 time is crucial.  
> 
> For example you can be running and screaming. Not only running then stopping to transition to screaming, then stop screaming and start running again.
> Maybe this is beyond the scope of a state machine, excuse if my terminology is off.

I'd say that your state machine can only be in one state at any given time
but you have a complex state as in multi dimensional and each combination
of the different dimensions counts as a different state.  So you have 4
discrete states: {not screaming, not running}, {not screaming, running},
{screaming, not running} and {screaming, running} but wants to reason about
the state as one complex term: {IsScreaming, IsRunning}.

> 
> If the implementation is quite simple using timers, I would not mind putting in the pull request.  I just came here to discuss about it and see if it is something that fits.
> 
> The rational is using erlang:send_after/3 is not enough in more complex cases, we need to also be stopping and starting new timers.  

Does not erlang:start_timer/3,4 work in this case?  You can have any number
of such timers simultaneously running just as for erlang:send_after/3,4.

> 
> An example of this is say if we are in a running state and every 100 ms we timeout, when we timeout we take an extra step and update our position in the world, also we determine how fast we are running and maybe the next step will be in 80ms now.
> 
> Now while we are running, we also got a moral boost by seeing a well lit street, so in 5000ms our moral boost will expire, and will timeout.
> 
> Also we start screaming due to panic, every 500ms we send a scream to all nearby recipients.
> 
> Maybe this is all beyond the scope of a state machine.  But for me it seems the only change required to support this is allowing multiple timers, and Erlang always had the design philosophy of "do what works" vs "do what is academically sound".

The question is if it is possible to create a timer concept in gen_statem
that is easier to use and still as flexible as erlang:start_timer/3,4 +
erlang:cancel_timer/1,2.

With those primitives you have to handle the TimerRef yourself.

Here is an attempt:

You return [{named_timeout,Time,Name}] and will get an event
named_timeout,Name.  If you return [{named_timeout,NewTime,Name}] with the
Name of a running timer it will restart with NewTime.  Therefore you can
cancel it with [{named_timeout,infinity,Name}].  Any number of these can
run simultaneously and you distinguish them by Name.

Or is the benefit of this i.e just to hide the TimerRef behind the timer
Msg/Name too small to be worthy of implementing?

/ Raimo


> 
> 
>  
> 
>     On Thursday, September 29, 2016 9:30 AM, Raimo Niskanen <raimo+erlang-questions@REDACTED> wrote:
>  
> 
>  After giving this a second thought i wonder if a single state timer would
> be a desired feature and enough in your case.
> 
> Today we have an event timeout, which is seldom useful since often you are
> in one state and wait for something specific while stray events that either
> are ignored or immediately replied to passes by.  The event timeout is
> reset for every stray event.
> 
> What I think would cover many use cases is a single state timeout.  It
> would be cancelled if you change states.  If you set it again the running
> timer is cancelled and a new is started.  There would only need to be one
> such timer so it is roughly as easy to implement as the event timeout.
> 
> There would be no way to cancel it other than changing states.
> It would be started with an action {state_timeout,T,Msg}.
> 
> We should keep the old {timeout,T,Msg} since it is inherited from gen_fsm
> and has some use cases.
> 
> What do you think?
> 
> / Raimo
> 
> 
> 
> On Mon, Sep 26, 2016 at 04:56:25PM +0200, Raimo Niskanen wrote:
> > On Sun, Sep 25, 2016 at 05:32:19PM +0000, Vans S wrote:
> > > Learning the new gen_statem made me desire for one extra feature.
> > > 
> > > Say you have a common use case of a webserver /w websockets, you have a general connection timeout of 120s, if no messages are received in 120s it means the socket is in an unknown state, and should be closed.
> > > 
> > > So you write your returns like this anytime the client sends you a message:
> > > 
> > > {next_state, NextState, NewData, {timeout, 120*1000, idle_timeout}}
> > > 
> > > Now if the client does not send any messages in 120 seconds, we will get a idle_timeout message sent to the gen_statem process.
> > > 
> > > Awesome.
> > > 
> > > But enter a little complexity, enter websockets.
> > > 
> > > Now we need to send a ping from the gen_statem every 15s to the client, but we also need to consider if we did not get any messages from the client in 120s, we are in unknown state and should terminate the connection.
> > > 
> > > So now we are just itching to do this on init:
> > > 
> > > {ok, initial_state, Data, [        {timeout, 120*1000, idle_timeout},        {timeout, 15*1000, websocket_ping}
> > >     ]}
> > > 
> > > This way we do not need to manage our own timers using erlang:send_after.  timer module is not even a consideration due to how inefficient it is at scaling.
> > > 
> > > But of course we cannot do this, the latest timeout will always override any previous.
> > > 
> > > What do you think?
> > 
> > Your use case is in the middle ground between the existing event timeout
> > and using erlang:start_timer/4,3, and is a special case of using
> > erlang:start_timer/4,3.
> > 
> > The existing {timeout,T,Msg} is an *event* timeout, so you get either an
> > event or the timeout.  The timer is cancelled by the first event.
> > This semantics is easy to reason about and has got a fairly simple
> > implementation in the state machine engine partly since it only needs
> > to store one timer ref.
> > 
> > It seems you could use a state timeout, i.e the timeout is cancelled when
> > the state changes.  This would require the state machine engine to hold any
> > number of timer refs and cancel all during a state change.
> > 
> > This semantics is subtly similar to the current event timeout.  It would
> > need a new option, e.g {state_timeout,T,Msg}.
> > 
> > The {state_timeout,_,_} semantics would be just a special case of using
> > erlang:start_timer/4,3, keep your timer ref in the server state and cancel
> > it when needed, since in the general case you might want to cancel the
> > timer at some other state change or maybe not a state change but an event.
> > 
> > So the question is if a {state_timeout,_,_} feature that auto cancels the
> > timer at the first state change is so useful that it is worthy of being
> > implemented?  It is not _that_ much code that is needed to store
> > a timer ref and cancel the timer started with erlang:start_timer/4,3,
> > and it is more flexible.
> > 
> > I implemented the {timeout,_,_} feature just so make it easy to port from
> > gen_fsm.  Otherwise I thought that using explicit timers was easy enough.
> > 

-- 

/ Raimo Niskanen, Erlang/OTP, Ericsson AB



More information about the erlang-questions mailing list