From 61935c77915a8e206a82cba1c9c9f64be462905d Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Thu, 21 Jun 2018 09:53:55 +0200 Subject: Clean up and optimize code and doc --- system/doc/design_principles/statem.xml | 277 ++++++++++++++++++-------------- 1 file changed, 160 insertions(+), 117 deletions(-) (limited to 'system/doc') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 98fd1fd69d..29e19163a5 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -44,27 +44,39 @@ Event-Driven State Machines

Established Automata Theory does not deal much with - how a state transition is triggered, + how a state transition is triggered, but assumes that the output is a function of the input (and the state) and that they are some kind of values.

For an Event-Driven State Machine, the input is an event - that triggers a state transition and the output - is actions executed during the state transition. + that triggers a state transition and the output + is actions executed during the state transition. It can analogously to the mathematical model of a - Finite-State Machine be described as + Finite State Machine be described as a set of relations of the following form:

 State(S) x Event(E) -> Actions(A), State(S')
-

These relations are interpreted as follows: +

+ These relations are interpreted as follows: if we are in state S and event E occurs, we are to perform actions A and make a transition to state S'. Notice that S' can be equal to S and that A can be empty.

+

+ In gen_statem we define + a state change as a state transition + in which the new state S' is different from + the current state S, where "different" means + Erlang's strict inequality: =/= + also know as "does not match". + During a state changes, + gen_statem does more things + than during other state transitions. +

As A and S' depend only on S and E, the kind of state machine described @@ -95,8 +107,8 @@ State(S) x Event(E) -> Actions(A), State(S') Co-located callback code for each state, - regardless of - Event Type + for all + Event Types (such as call, cast and info) @@ -114,13 +126,13 @@ State(S) x Event(E) -> Actions(A), State(S') - State Enter Calls + State Enter Calls (callback on state entry co-located with the rest of each state's callback code) - Easy-to-use timeouts + Easy-to-use time-outs (State Time-Outs, Event Time-Outs and @@ -152,11 +164,11 @@ State(S) x Event(E) -> Actions(A), State(S') Callback Module

- The callback module contains functions that implement + The callback module contains functions that implement the state machine. When an event occurs, the gen_statem behaviour engine - calls a function in the callback module with the event, + calls a function in the callback module with the event, current state and server data. This function performs the actions for this event, and returns the new state and server data @@ -166,7 +178,7 @@ State(S) x Event(E) -> Actions(A), State(S') The behaviour engine holds the state machine state, server data, timer references, a queue of posponed messages and other metadata. It receives all process messages, - handles the system messages, and calls the callback module + handles the system messages, and calls the callback module with machine specific events.

@@ -177,7 +189,7 @@ State(S) x Event(E) -> Actions(A), State(S') Callback Modes

- The gen_statem behavior supports two callback modes: + The gen_statem behavior supports two callback modes:

@@ -202,31 +214,33 @@ State(S) x Event(E) -> Actions(A), State(S')

- The callback mode is selected at server start + The callback mode is selected at server start and may be changed with a code upgrade/downgrade.

See the section - Event Handler + State Callback that describes the event handling callback function(s).

- The callback mode is selected by implementing + The callback mode is selected by implementing a mandatory callback function Module:callback_mode() - that returns one of the callback modes. + that returns one of the callback modes.

The Module:callback_mode() - function may also return a list containing the callback mode + function may also return a list containing the callback mode and the atom state_enter in which case - State Enter Calls - are activated for the callback mode. + + state enter calls + + are activated for the callback mode.

@@ -237,11 +251,11 @@ State(S) x Event(E) -> Actions(A), State(S') it is the one most like gen_fsm. But if you do not want the restriction that the state must be an atom, or if you do not want to write - one event handler function per state; please read on... + one state callback function per state; please read on...

The two - Callback Modes + callback modes give different possibilities and restrictions, with one common goal: to handle all possible combinations of events and states. @@ -257,7 +271,7 @@ State(S) x Event(E) -> Actions(A), State(S') With state_functions, you are restricted to use atom-only states, and the gen_statem engine branches depending on state name for you. - This encourages the callback module to co-locate + This encourages the callback module to co-locate the implementation of all event actions particular to one state in the same place in the code, hence to focus on one state at the time. @@ -302,11 +316,12 @@ State(S) x Event(E) -> Actions(A), State(S')

- - Event Handler + + State Callback

- Which callback function that handles an event - depends on the callback mode: + The state callback is the callback function + that handles an event in the current state, + and which function that is depends on the callback mode:

state_functions @@ -329,7 +344,9 @@ State(S) x Event(E) -> Actions(A), State(S')

See section - One Event Handler + + One State Callback + for an example.

@@ -338,15 +355,17 @@ State(S) x Event(E) -> Actions(A), State(S') The state is either the name of the function itself or an argument to it. The other arguments are the EventType described in section Event Types, - the event dependent EventContent, and the current server Data. + the event dependent EventContent, + and the current server Data.

- State enter calls are also handled by the event handler and have - slightly different arguments. See the section + State enter calls are also handled by the event handler + and have slightly different arguments. See section State Enter Calls.

- The event handler return values are defined in the description of + The state callback return values + are defined in the description of Module:StateName/3 @@ -361,24 +380,29 @@ State(S) x Event(E) -> Actions(A), State(S')

Set next state and update the server data. - If the Actions field is used, execute state transition actions. - An empty Actions list is equivalent to not returning the field. + If the Actions field is used, + execute transition actions. + An empty Actions list is equivalent to + not returning the field.

See section - - State Transition Actions + + Transition Actions for a list of possible - state transition actions. + transition actions.

- If NextState =/= State the state machine changes - to a new state. A + If NextState =/= State this is a state change + so the extra things gen_statem does are: the event queue + is restarted from the oldest + postponed event, + any current + state time-out + is cancelled, and a state enter call - is performed if enabled and all - postponed events - are retried. + is performed, if enabled.

@@ -388,7 +412,7 @@ State(S) x Event(E) -> Actions(A), State(S')

Same as the next_state values with - NextState =:= State, that is, no state change. + NextState =:= State, that is, no state change.

@@ -414,9 +438,16 @@ State(S) x Event(E) -> Actions(A), State(S') State Enter Calls - are enabled, repeat the state enter call + are enabled, repeat the state enter call as if this state was entered again.

+

+ If these return values are used from a + state enter call the OldState does not change, + but if used from an event handling state callback + the new state enter call's OldState + will be the current state. +

{stop, Reason, NewData}
@@ -435,7 +466,10 @@ State(S) x Event(E) -> Actions(A), State(S')

Same as the stop values, but first execute the given - state transition actions that may only be reply actions. + + transition actions + + that may only be reply actions.

@@ -449,8 +483,8 @@ State(S) x Event(E) -> Actions(A), State(S') Module:init(Args) callback function is called before any - Event Handler - is called. This function behaves like an event handler + state callback + is called. This function behaves like an state callback function, but gets its only argument Args from the gen_statem @@ -474,8 +508,8 @@ State(S) x Event(E) -> Actions(A), State(S')
- - State Transition Actions + + Transition Actions

In the first section @@ -483,13 +517,13 @@ State(S) x Event(E) -> Actions(A), State(S') actions were mentioned as a part of the general state machine model. These general actions - are implemented with the code that callback module + are implemented with the code that callback module gen_statem executes in an event-handling callback function before returning to the gen_statem engine.

- There are more specific state-transition actions + There are more specific transition actions that a callback function can command the gen_statem engine to do after the callback function return. These are commanded by returning a list of @@ -500,7 +534,7 @@ State(S) x Event(E) -> Actions(A), State(S') from the callback function. - These are the possible state transition actions: + These are the possible transition actions:

@@ -596,13 +630,13 @@ State(S) x Event(E) -> Actions(A), State(S') Event Types

Events are categorized in different - event types. + event types. Events of all types are for a given state handled in the same callback function, and that function gets EventType and EventContent as arguments.

- The following is a complete list of event types and where + The following is a complete list of event types and where they come from:

@@ -624,7 +658,7 @@ State(S) x Event(E) -> Actions(A), State(S') Generated by gen_statem:call, where From is the reply address to use - when replying either through the state transition action + when replying either through the transition action {reply,From,Msg} or by calling gen_statem:reply. @@ -643,7 +677,7 @@ State(S) x Event(E) -> Actions(A), State(S')
- Generated by state transition action + Generated by transition action {state_timeout,Time,EventContent} @@ -655,7 +689,7 @@ State(S) x Event(E) -> Actions(A), State(S') - Generated by state transition action + Generated by transition action {{timeout,Name},Time,EventContent} @@ -667,7 +701,7 @@ State(S) x Event(E) -> Actions(A), State(S') - Generated by state transition action + Generated by transition action {timeout,Time,EventContent} @@ -680,10 +714,10 @@ State(S) x Event(E) -> Actions(A), State(S') - Generated by state transition + Generated by state transition action {next_event,internal,EventContent}. - All event types above can also be generated using + All event types above can also be generated using {next_event,EventType,EventContent}.
@@ -696,14 +730,14 @@ State(S) x Event(E) -> Actions(A), State(S') State Enter Calls

The gen_statem behavior can if this is enabled, - regardless of callback mode, + regardless of callback mode, automatically call the state callback with special arguments whenever the state changes so you can write state enter actions - near the rest of the state transition rules. + near the rest of the state transition rules. It typically looks like this:

@@ -714,33 +748,35 @@ StateName(EventType, EventContent, Data) ->
     ... code for actions here ...
     {next_state, NewStateName, NewData}.

- Since the state enter call is not an event there are restrictions + Since the state enter call is not an event there are restrictions on the allowed return value and State Transition Actions. You may not change the state, postpone this non-event, or - insert events. + insert any events.

- The first state that is entered will get a state enter call + The first state that is entered + will get a state enter call with OldState equal to the current state.

- You may repeat the state enter call using the {repeat_state,...} + You may repeat the state enter call + using the {repeat_state,...} return value from the - Event Handler. + state callback. In this case OldState will also be equal to the current state.

Depending on how your state machine is specified, - this can be a very useful feature, - but it forces you to handle the state enter calls in all states. + this can be a very useful feature, but it forces you to handle + the state enter calls in all states. See also the State Enter Actions - chapter. + section.

@@ -765,7 +801,7 @@ StateName(EventType, EventContent, Data) ->

This code lock state machine can be implemented using - gen_statem with the following callback module: + gen_statem with the following callback module:

The second argument, ?MODULE, is the name of - the callback module, that is, the module where the callback + the callback module, that is, + the module where the callback functions are located, which is this module.

@@ -935,7 +972,7 @@ init(Code) -> Module:callback_mode/0 selects the CallbackMode - for the callback module, in this case + for the callback module, in this case state_functions. That is, each state has got its own handler function:

@@ -1051,11 +1088,11 @@ open(state_timeout, lock, Data) -> ]]>

The timer for a state time-out is automatically cancelled - when the state machine changes states. You can restart - a state time-out by setting it to a new time, which cancels - the running timer and starts a new. This implies that - you can cancel a state time-out by restarting it with - time infinity. + when the state machine does a state change. + You can restart a state time-out by setting it to a new time, + which cancels the running timer and starts a new. + This implies that you can cancel a state time-out + by restarting it with time infinity.

@@ -1137,7 +1174,7 @@ open(...) -> ... ; care about what it is.

- If the common event handler needs to know the current state + If the common state callback needs to know the current state a function handle_common/4 can be used instead:

... ;
- - One Event Handler + + One State Callback

If - Callback Mode + callback mode handle_event_function is used, all events are handled in @@ -1289,7 +1326,10 @@ stop() -> You get either an event or a time-out, but not both.

- It is ordered by the state transition action + It is ordered by the + + transition action + {timeout,Time,EventContent}, or just an integer Time, even without the enclosing actions list (the latter is a form inherited from gen_fsm. @@ -1315,7 +1355,7 @@ locked( ]]>

Whenever we receive a button event we start an event time-out - of 30 seconds, and if we get an event type timeout + of 30 seconds, and if we get an event type of timeout we reset the remaining code sequence.

@@ -1327,7 +1367,7 @@ locked(

Note that an event time-out does not work well with - when you have for example a status call as in + when you have for example a status call as in section All State Events, or handle unknown events, since all kinds of events will cancel the event time-out. @@ -1383,14 +1423,14 @@ open(cast, {button,_}, Data) -> ]]>

Specific generic time-outs can just as - State Time-Outs + state time-outs be restarted or cancelled by setting it to a new time or infinity.

- In this particular case we do not need to cancel the timeout - since the timeout event is the only possible reason to - change the state from open to locked. + In this particular case we do not need to cancel the time-out + since the time-out event is the only possible reason to + do a state change from open to locked.

Instead of bothering with when to cancel a time-out, @@ -1442,7 +1482,7 @@ open(cast, {button,_}, Data) -> ]]>

Removing the timer key from the map when we - change to state locked is not strictly + do a state change to locked is not strictly necessary since we can only get into state open with an updated timer map value. But it can be nice to not have outdated values in the state Data! @@ -1474,13 +1514,13 @@ open(cast, {button,_}, Data) ->

If you want to ignore a particular event in the current state and handle it in a future state, you can postpone the event. - A postponed event is retried after the state has - changed, that is, OldState =/= NewState. + A postponed event is retried after a state change, + that is, OldState =/= NewState.

- Postponing is ordered by the state transition - - State Transition Action + Postponing is ordered by the + + transition action postpone.

@@ -1496,7 +1536,8 @@ open(cast, {button,_}, Data) -> ... ]]>

- Since a postponed event is only retried after a state change, + Since a postponed event is only retried + after a state change, you have to think about where to keep a state data item. You can keep it in the server Data or in the State itself, @@ -1505,7 +1546,7 @@ open(cast, {button,_}, Data) -> (see section Complex State) with - Callback Mode + callback mode handle_event_function. If a change in the value changes the set of events that is handled, then the value should be kept in the State. @@ -1606,17 +1647,17 @@ do_unlock() -> sys compatible behaviors must respond to system messages and therefore do that in their engine receive loop, - passing non-system messages to the callback module. + passing non-system messages to the callback module.

The - - State Transition Action + + transition action postpone is designed to model selective receives. A selective receive implicitly postpones any not received events, but the postpone - state transition action explicitly postpones one received event. + transition action explicitly postpones one received event.

Both mechanisms have the same theoretical @@ -1638,14 +1679,17 @@ do_unlock() -> (described in the next section), especially if just one or a few states has got state enter actions, this is a perfect use case for the built in - State Enter Calls. + state enter calls.

You return a list containing state_enter from your - callback_mode/0 + + callback_mode/0 + function and the gen_statem engine will call your - state callback once with the arguments - (enter, OldState, ...) whenever the state changes. + state callback once with an event + (enter, OldState, ...) + whenever it does a state change. Then you just need to handle these event-like calls in all states.

It can sometimes be beneficial to be able to generate events to your own state machine. This can be done with the - - State Transition Action + + transition action {next_event,EventType,EventContent}.

@@ -1731,11 +1775,9 @@ open(state_timeout, lock, Data) ->

A variant of this is to use a - - Complex State - + complex state with - One Event Handler. + one state callback. The state is then modeled with for example a tuple {MainFSMState,SubFSMState}.

@@ -1795,7 +1837,7 @@ open(internal, {button,_}, Data) -> Example Revisited

This section includes the example after most of the mentioned - modifications and some more using state enter calls, + modifications and some more using state enter calls, which deserves a new state diagram:

+ +
+ + Time-outs +

+ Time-outs in gen_statem are started from a + + transition action + + during a state transition that is when exiting from the + state callback. +

+

+ There are 3 types of time-outs in gen_statem: +

+ + + + state_timeout + + + + There is one + State Time-Out + that is automatically cancelled by a state change. + + + + {timeout, Name} + + + + There are any number of + Generic Time-Outs + differing by their Name. + They have no automatic cancelling. + + + + timeout + + + + There is one + Event Time-Out + that is automatically cancelled by any event. + Note that + postponed + and + inserted + events cancel this timeout just as external events. + + +

+ When a time-out is started any running time-out with the same tag, + state_timeout, {timeout, Name} or timeout, + is cancelled, that is the time-out is restarted with the new time. +

+

+ All time-outs has got an EventContent that is part of the + + transition action + + that starts the time-out. + Different EventContents does not create different time-outs. + The EventContent is delivered to the + state callback + when the time-out expires. +

+
+ + Cancelling a Time-Out +

+ If a time-out is started with the time infinity it will + never time out, in fact it will not even be started, and any + running time-out with the same tag will be cancelled. + The EventContent will in this case be ignored, + so why not set it to undefined. +

+
+
+ + Time-Out Zero +

+ If a time-out is started with the time 0 it will + actually not be started. Instead the time-out event will + immediately be inserted to be processed after any events + already enqueued, and before any not yet received external events. + Note that some time-outs are automatically cancelled + so if you for example combine + postponing + an event in a state change with starting an + event time-out + with time 0 there will be no timeout event inserted + since the event time-out is cancelled by the postponed + event that is delivered due to the state change. +

+
+
+ +
-- cgit v1.2.3