From 09d138e229846b7056331151135b7c8a52dc476f Mon Sep 17 00:00:00 2001 From: xsipewe Date: Fri, 6 May 2016 09:55:25 +0200 Subject: Editorial update --- system/doc/design_principles/statem.xml | 696 ++++++++++++++++---------------- 1 file changed, 349 insertions(+), 347 deletions(-) (limited to 'system/doc/design_principles/statem.xml') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index ca0fce55e2..8b0fbed7c0 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -5,7 +5,7 @@
2016 - Ericsson AB. All Rights Reserved. + Ericsson AB. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ - gen_statem Behaviour + gen_statem Behavior @@ -33,63 +33,60 @@

This section is to be read with the gen_statem(3) - manual page in STDLIB, where all interface functions and callback + manual page in STDLIB, where all interface functions and callback functions are described in detail.

-

- This is a new behaviour in OTP-19.0. - It has been thoroughly reviewed, is stable enough - to be used by at least two heavy OTP applications, and is here to stay. - But depending on user feedback, we do not expect - but might find it necessary to make minor - not backwards compatible changes into OTP-20.0, - so its state can be designated as "not quite experimental"... -

+ +

+ This is a new behavior in Erlang/OTP 19.0. + It has been thoroughly reviewed, is stable enough + to be used by at least two heavy OTP applications, and is here to stay. + Depending on user feedback, we do not expect + but can find it necessary to make minor + not backward compatible changes into Erlang/OTP 20.0. +

+
- Event Driven State Machines + Event-Driven State Machines

Established Automata theory does not deal much with how a state transition is triggered, - but in general assumes that the output is a function + 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 + 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. It can analogously to the mathematical model of a - Finite State Machine be described as - a set of relations of the form: + 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 meaning:

-

- If we are in state S and event E occurs, we +

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'. -

-

- Note that S' may be equal to S. + state S'. Notice that S' can be equal to S.

- Since A and S' depend only on - S and E the kind of state machine described - here is a Mealy Machine. - (See for example the corresponding Wikipedia article) + As A and S' depend only on + S and E, the kind of state machine described + here is a Mealy Machine + (see, for example, the corresponding Wikipedia article).

- Like most gen_ behaviours, gen_statem keeps - a server Data besides the state. This and the fact that + Like most gen_ behaviors, gen_statem keeps + a server Data besides the state. Because of this, and as there is no restriction on the number of states - (assuming enough virtual machine memory) - or on the number of distinct input events actually makes - a state machine implemented with this behaviour Turing complete. - But it feels mostly like an Event Driven Mealy Machine. + (assuming that there is enough virtual machine memory) + or on the number of distinct input events, makes + a state machine implemented with this behavior Turing complete. + But it feels mostly like an Event-Driven Mealy Machine.

@@ -99,38 +96,40 @@ State(S) x Event(E) -> Actions(A), State(S') Callback Modes

- The gen_statem behaviour supports two different callback modes. - In the mode - - state_functions, - - the state transition rules are written as a number of Erlang - functions, which conform to the following convention: + The gen_statem behavior supports two callback modes:

-
+    
+      
+        

+ In mode + state_functions, + the state transition rules are written as some Erlang + functions, which conform to the following convention: +

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

- In the mode - - handle_event_function - - there is only one - Erlang function that implements all state transition rules: -

-
+      
+      
+        

+ In mode + handle_event_function, + only one Erlang function provides all state transition rules: +

+
 handle_event(EventType, EventContent, State, Data) ->
     .. code for actions here ...
     {next_state, State', Data'}
+
+

- Both these modes allow other return tuples - that you can find in the + Both these modes allow other return tuples; see - reference manual. - - These other return tuples can for example stop the machine, - execute state transition actions on the machine engine itself + Module:StateName/3 + in the gen_statem manual page. + These other return tuples can, for example, stop the machine, + execute state transition actions on the machine engine itself, and send replies.

@@ -139,54 +138,55 @@ handle_event(EventType, EventContent, State, Data) ->

The two callback modes - gives different possibilities + give different possibilities and restrictions, but one goal remains: you want to handle all possible combinations of events and states.

- You can for example do this by focusing on one state at the time - and for every state ensure that all events are handled, - or the other way around focus on one event at the time - and ensure that it is handled in every state, - or mix these strategies. + This can be done, for example, by focusing on one state at the time + and for every state ensure that all events are handled. + Alternatively, you can focus on one event at the time + and ensure that it is handled in every state. + You can also use a mix of these strategies.

- With state_functions you are restricted to use - atom only states, and the gen_statem engine dispatches - on state name for you. This encourages the callback module + With state_functions, you are restricted to use + atom-only states, and the gen_statem engine dispatches + on state name for you. This encourages the callback module to gather the implementation of all event actions particular - to one state in the same place in the code + to one state in the same place in the code, hence to focus on one state at the time.

- This mode fits well when you have a regular state diagram - like the ones in this chapter that describes all events and actions + This mode fits well when you have a regular state diagram, + like the ones in this chapter, which describes all events and actions belonging to a state visually around that state, and each state has its unique name.

- With handle_event_function you are free to mix strategies - as you like because all events and states - are handled in the the same callback function. + With handle_event_function, you are free to mix strategies, + as all events and states are handled in the same callback function.

This mode works equally well when you want to focus on - one event at the time or when you want to focus on - one state at the time, but the handle_event/4 function + one event at the time or on + one state at the time, but function + + Module:handle_event/4 quickly grows too large to handle without introducing dispatching.

- The mode enables the use of non-atom states for example - complex states or even hiearchical states. + The mode enables the use of non-atom states, for example, + complex states or even hierarchical states. If, for example, a state diagram is largely alike - for the client and for the server side of a protocol; - then you can have a state {StateName,server} or - {StateName,client} and since you do the dispatching - yourself you make StateName decide where in the code + for the client side and the server side of a protocol, + you can have a state {StateName,server} or + {StateName,client}. Also, as you do the dispatching + yourself, you make StateName decide where in the code to handle most events in the state. - The second element of the tuple is then used to select - whether to handle special client side or server side events. + The second element of the tuple is then used to select + whether to handle special client-side or server-side events.

@@ -196,31 +196,28 @@ handle_event(EventType, EventContent, State, Data) ->
Example

- This is an example starting off as equivalent to the the example in the - gen_fsm behaviour - description. In later chapters additions and tweaks are made + This example starts off as equivalent to the example in section + gen_fsm Behavior. + In later sections, additions and tweaks are made using features in gen_statem that gen_fsm does not have. - At the end of this section you can find the example again + The end of this chapter provides the example again with all the added features.

- A door with a code lock can be viewed as a state machine. - Initially, the door is locked. Anytime someone presses a button, - this generates an event. + A door with a code lock can be seen as a state machine. + Initially, the door is locked. When someone presses a button, + an event is generated. Depending on what buttons have been pressed before, the sequence so far can be correct, incomplete, or wrong. -

-

- If it is correct, the door is unlocked for 10 seconds (10000 ms). - If it is incomplete, we wait for another button to be pressed. If - it is is wrong, we start all over, - waiting for a new button sequence. + If correct, the door is unlocked for 10 seconds (10,000 milliseconds). + If incomplete, we wait for another button to be pressed. If + wrong, we start all over, waiting for a new button sequence.

- Code lock state diagram + Code Lock State Diagram

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

@@ -241,7 +238,6 @@ start_link(Code) -> button(Digit) -> gen_statem:cast(?NAME, {button,Digit}). - init(Code) -> do_lock(), Data = #{code => Code, remaining => Code}, @@ -286,7 +282,7 @@ code_change(_Vsn, State, Data, _Extra) ->
Starting gen_statem

- In the example in the previous section, the gen_statem is + In the example in the previous section, gen_statem is started by calling code_lock:start_link(Code):

gen_statem:start_link({local,?NAME}, ?MODULE, Code, []). ]]>

- start_link calls the function + start_link calls function gen_statem:start_link/4 - - which spawns and links to a new process; a gen_statem. + , + which spawns and links to a new process, a gen_statem.

- The first argument, {local,?NAME}, specifies - the name. In this case, the gen_statem is locally - registered as code_lock through the macro ?NAME. -

+ The first argument, {local,?NAME}, specifies + the name. In this case, the gen_statem is locally + registered as code_lock through the macro ?NAME. +

- If the name is omitted, the gen_statem is not registered. - Instead its pid must be used. The name can also be given - as {global,Name}, in which case the gen_statem is - registered using - - global:register_name/2. - -

+ If the name is omitted, the gen_statem is not registered. + Instead its pid must be used. The name can also be specified + as {global,Name}, then the gen_statem is + registered using + + global:register_name/2 + + in Kernel. +

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

+

- The interface functions (start_link/1 and button/1) - are located in the same module as the callback functions - (init/1, locked/3, and open/3). - It is normally good programming practice to have the client - side and the server side code contained in one module. -

+ The interface functions (start_link/1 and button/1) + are located in the same module as the callback functions + (init/1, locked/3, and open/3). + It is normally good programming practice to have the client-side + code and the server-side code contained in one module. +

- The third argument, Code, is a list of digits that - is the correct unlock code which is passsed - to the callback function init/1. + The third argument, Code, is a list of digits, which + is the correct unlock code that is passed + to callback function init/1.

- The fourth argument, [], is a list of options. See the - - gen_statem:start_link/3 - - manual page for available options. + The fourth argument, [], is a list of options. + For the available options, see + + gen_statem:start_link/3 + .

If name registration succeeds, the new gen_statem process - calls the callback function code_lock:init(Code). + calls callback function code_lock:init(Code). This function is expected to return {CallbackMode,State,Data}, where @@ -360,14 +357,14 @@ start_link(Code) -> state_functions - through the macro ?CALLBACK_MODE that is; each state + through macro ?CALLBACK_MODE. That is, each state has got its own handler function. State is the initial state of the gen_statem, - in this case locked; assuming the door is locked to begin with. - Data is the internal server data of the gen_statem. + in this case locked; assuming that the door is locked to begin + with. Data is the internal server data of the gen_statem. Here the server data is a map - with the key code that stores - the correct button sequence and the key remaining + with key code that stores + the correct button sequence, and key remaining that stores the remaining correct button sequence (the same as the code to begin with).

@@ -377,24 +374,25 @@ init(Code) -> Data = #{code => Code, remaining => Code}, {?CALLBACK_MODE,locked,Data}. ]]> -

+

Function gen_statem:start_link - is synchronous. It does not return until the gen_statem - has been initialized and is ready to receive events. + is synchronous. It does not return until the gen_statem + is initialized and is ready to receive events.

+ Function gen_statem:start_link must be used if the gen_statem - is part of a supervision tree, that is; started by a supervisor. - There is another function; + is part of a supervision tree, that is, started by a supervisor. + Another function, gen_statem:start - to start a standalone gen_statem, that is; + can be used to start a standalone gen_statem, that is, a gen_statem that is not part of a supervision tree.

@@ -402,7 +400,7 @@ init(Code) ->
- Events and Handling them + Handling Events

The function notifying the code lock about a button event is implemented using @@ -415,9 +413,9 @@ button(Digit) -> ]]>

The first argument is the name of the gen_statem and must - agree with the name used to start it so therefore we use the + agree with the name used to start it. So, we use the same macro ?NAME as when starting. - {button,Digit} is the actual event content. + {button,Digit} is the event content.

The event is made into a message and sent to the gen_statem. @@ -452,19 +450,19 @@ open(cast, {button,_}, Data) -> ]]>

If the door is locked and a button is pressed, the pressed - button is compared with the next correct button and, - depending on the result, the door is either unlocked + button is compared with the next correct button. + Depending on the result, the door is either unlocked and the gen_statem goes to state open, or the door remains in state locked.

- If the pressed button is incorrect the server data + If the pressed button is incorrect, the server data restarts from the start of the code sequence.

- In state open any button locks the door since - any event cancels the event timer so we will not get - a timeout event after a button event. + In state open, any button locks the door, as + any event cancels the event timer, so no + time-out event occurs after a button event.

@@ -478,11 +476,11 @@ open(cast, {button,_}, Data) -> {next_state,open,Data#{remaining := Code},10000}; ]]>

- 10000 is a time-out value in milliseconds. - After this time, that is; 10 seconds, a time-out occurs. + 10,000 is a time-out value in milliseconds. + After this time (10 seconds), a time-out occurs. Then, StateName(timeout, 10000, Data) is called. The time-out occurs when the door has been in state open - for 10 seconds. After that the door is locked again: + for 10 seconds. After that the door is locked again:

@@ -496,16 +494,16 @@ open(timeout, _, Data) ->
All State Events

- Sometimes an event can arrive in any state of the gen_statem. + Sometimes events can arrive in any state of the gen_statem. It is convenient to handle these in a common state handler function that all state functions call for events not specific to the state.

- Let's introduce a code_length/0 function that returns + Consider a code_length/0 function that returns the length of the correct code - (that should not be sensitive to reveal...). - We'll dispatch all events that are not state specific - to the common function handle_event/3. + (that should not be sensitive to reveal). + We dispatch all events that are not state-specific + to the common function handle_event/3:

This example uses gen_statem:call/2 - + , which waits for a reply from the server. The reply is sent with a {reply,From,Reply} tuple in an action list in the {keep_state,...} tuple @@ -545,9 +543,12 @@ handle_event({call,From}, code_length, #{code := Code} = Data) ->
One Event Handler

- If you use the mode handle_event_function - all events are handled in handle_event/4 and we - may (but do not have to) use an event-centered approach + If mode handle_event_function is used, + all events are handled in + + Module:handle_event/4 + + and we can (but do not have to) use an event-centered approach where we dispatch on event first and then state:

The gen_statem is automatically terminated by its supervisor. Exactly how this is done is defined by a shutdown strategy - set in the supervisor. + set in the supervisor.

If it is necessary to clean up before termination, the shutdown - strategy must be a time-out value and the gen_statem must - in the init/1 function set itself to trap exit signals + strategy must be a time-out value and the gen_statem must + in function init/1 set itself to trap exit signals by calling - process_flag(trap_exit, true). - - When ordered to shutdown, the gen_statem then calls - the callback function - terminate(shutdown, State, Data): + process_flag(trap_exit, true) + . + When ordered to shut down, the gen_statem then calls + callback function terminate(shutdown, State, Data):

@@ -617,9 +617,9 @@ init(Args) -> ... ]]>

- In this example we let the terminate/3 function - lock the door if it is open so we do not accidentally leave the door - open when the supervision tree terminates. + In the following example, function terminate/3 + locks the door if it is open, so we do not accidentally leave the door + open when the supervision tree terminates:

@@ -634,8 +634,8 @@ terminate(_Reason, State, _Data) -> If the gen_statem is not part of a supervision tree, it can be stopped using - gen_statem:stop, - + gen_statem:stop + , preferably through an API function:

gen_statem:stop(?NAME). ]]>

- This makes the gen_statem call the terminate/3 - callback function just like for a supervised server + This makes the gen_statem call callback function + terminate/3 just like for a supervised server and waits for the process to terminate.

@@ -659,15 +659,15 @@ stop() ->
Actions

- In the first chapters we mentioned actions as a part of - the general state machine model, and these actions - are implemented with the code the gen_statem - callback module executes in an event handling + In the first sections actions were mentioned as a part of + the general state machine model. These actions + 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 state-transition actions that a callback function can order the gen_statem engine to do after the callback function return. These are ordered by returning a list of @@ -680,27 +680,28 @@ stop() -> from the - callback function. - + callback function + . These state transition actions affect the gen_statem - engine itself. They can: + engine itself and can do the following:

- Postpone the current event. - Hibernate the gen_statem. - Start an event timeout. - Reply to a caller. - Generate the next event to handle. + Postpone the current event + Hibernate the gen_statem + Start an event time-out + Reply to a caller + Generate the next event to handle

- We have mentioned the event timeout - and replying to a caller in the example above. - An example of event postponing comes in later in this chapter. - See the + In the example earlier was mentioned the event time-out + and replying to a caller. + An example of event postponing is included later in this chapter. + For details, see the - reference manual + gen_statem(3) - for details. You can for example actually reply to several callers + manual page. + You can, for example, reply to many callers and generate multiple next events to handle.

@@ -710,16 +711,16 @@ stop() ->
Event Types

- So far we have mentioned a few + The previous sections mentioned a few - event types. - + event types + . Events of all types are handled in the same callback function, for a given state, and the function gets EventType and EventContent as arguments.

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

@@ -735,13 +736,13 @@ stop() -> Generated by gen_statem:call - + , where From is the reply address to use when replying either through the state transition action {reply,From,Msg} or by calling - gen_statem:reply. - + gen_statem:reply + . info @@ -750,15 +751,15 @@ stop() -> timeout - Generated by the state transition action + Generated by state transition action {timeout,Time,EventContent} (or its short form Time) timer timing out. internal - Generated by the state transition action + Generated by state transition action {next_event,internal,EventContent}. - In fact all event types above can be generated using + All event types above can be generated using {next_event,EventType,EventContent}. @@ -767,34 +768,34 @@ stop() ->
- State Timeouts + State Time-Outs

- The timeout event generated by the state transition action - {timeout,Time,EventContent} is an event timeout, - that is; if an event arrives the timer is cancelled. - You get either an event or a timeout but not both. + The time-out event generated by state transition action + {timeout,Time,EventContent} is an event time-out, + that is, if an event arrives the timer is cancelled. + You get either an event or a time-out, but not both.

- Often you want a timer to not be cancelled by any event + Often you want a timer not to be cancelled by any event or you want to start a timer in one state and respond - to the timeout in another. This can be accomplished - with a regular erlang timer: + to the time-out in another. This can be accomplished + with a regular Erlang timer: erlang:start_timer.

- Looking at the example in this chapter so far; using the + For the example so far in this chapter: using the gen_statem event timer has the consequence that if a button event is generated while in the open state, - the timeout is cancelled and the button event is delivered. - Therefore we chose to lock the door if this happended. + the time-out is cancelled and the button event is delivered. + So, we choose to lock the door if this occurred.

- Suppose we do not want a button to lock the door, + Suppose that we do not want a button to lock the door, instead we want to ignore button events in the open state. Then we start a timer when entering the open state - and wait for it to expire while ignoring button events: + and waits for it to expire while ignoring button events:

... ]]>

- If you need to cancel a timer due to some other event you can use + If you need to cancel a timer because of some other event, you can use - erlang:cancel_timer(Tref). - - Note that a timeout message can not arrive after this, + erlang:cancel_timer(Tref) + . + Notice that a time-out message cannot arrive after this, unless you have postponed it (see the next section) before, - so make sure you do not accidentally postpone such messages. + so ensure that you do not accidentally postpone such messages.

- Another way to cancel a timer is to not cancel it, - but instead to ignore it if it arrives in a state + Another way to cancel a timer is not to cancel it, + but to ignore it if it arrives in a state where it is known to be late.

@@ -839,7 +840,7 @@ 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 i.e OldState =/= NewState. + changed, that is, OldState =/= NewState.

Postponing is ordered by the state transition @@ -850,8 +851,8 @@ open(cast, {button,_}, Data) ->

In this example, instead of ignoring button events - while in the open state we can postpone them - and they will be queued and later handled in the locked state: + while in the open state, we can postpone them + and they are queued and later handled in the locked state:

... ]]>

- The fact that a postponed event is only retried after a state change - translates into a requirement on the event and state space: - if you have a choice between storing a state data item - in the State or in the Data; - should a change in the item value affect which events that - are handled, then this item ought to be part of the state. + A postponed event is only retried after a state change + translates into a requirement on the event and state space. + If you have a choice between storing a state data item + in the State or in the Data: + if a change in the item value affects which events that + are handled, then this item is to be part of the state.

- What you want to avoid is that you maybe much later decide - to postpone an event in one state and by misfortune it is never retried - because the code only changes the Data but not the State. + You want to avoid that you maybe much later decide + to postpone an event in one state and by misfortune it is never retried, + as the code only changes the Data but not the State.

@@ -883,7 +884,7 @@ open(cast, {button,_}, Data) -> or from the context.

- Possible actions may be; ignore as in drop the event + Possible actions: ignore as in drop the event (maybe log it) or deal with the event in some other state as in postpone it.

@@ -892,10 +893,10 @@ open(cast, {button,_}, Data) ->
Selective Receive

- Erlang's selective receive statement is often used to - describe simple state machine examples in straightforward - Erlang code. Here is a possible implementation of - the first example: + Erlang's selective receive statement is often used to + describe simple state machine examples in straightforward + Erlang code. The following is a possible implementation of + the first example:

io:format("Open~n", []). ]]>

- The selective receive in this case causes open - to implicitly postpone any events to the locked state. + The selective receive in this case causes implicitly open + to postpone any events to the locked state.

- A selective receive can not be used from a gen_statem - behaviour just as for any gen_* behavior - since the receive statement is within the gen_* engine itself. - It has to be there because all + A selective receive cannot be used from a gen_statem + behavior as for any gen_* behavior, + as the receive statement is within the gen_* engine itself. + It must be there because all sys - compatible behaviours must respond to system messages and therefore + compatible behaviors must respond to system messages and therefore do that in their engine receive loop, passing non-system messages to the callback module.

@@ -955,15 +956,15 @@ do_unlock() -> action - postpone is designed to be able to model - selective receives. A selective receive implicitly postpones + 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.

- Other than that both mechanisms have got the same theoretical + Both mechanisms have the same theoretical time and memory complexity, while the selective receive - language construct has got smaller constant factors. + language construct has smaller constant factors.

@@ -971,9 +972,9 @@ do_unlock() ->
- Self Generated Events + Self-Generated Events

- It may be beneficial in some cases to be able to generate events + It can sometimes be beneficial to be able to generate events to your own state machine. This can be done with the state transition @@ -984,30 +985,30 @@ do_unlock() ->

You can generate events of any existing - type, - - but the internal type can only be generated through the - next_event action and hence can not come from an external source, + type + , + but the internal type can only be generated through action + next_event. Hence, it cannot come from an external source, so you can be certain that an internal event is an event from your state machine to itself.

- One example of using self generated events may be when you have + One example of using self-generated events can be when you have a state machine specification that uses state entry actions. - That you could code using a dedicated function - to do the state transition. But if you want that code to be - visible besides the other state logic you can insert + You can code that using a dedicated function + to do the state transition. But if you want that code to be + visible besides the other state logic, you can insert an internal event that does the entry actions. This has the same unfortunate consequence as using - state transition functions that everywhere you go to - the state in question you will have to explicitly + state transition functions: everywhere you go to + the state, you must explicitly insert the internal event - or use state transition function. + or use a state transition function.

- Here is an implementation of entry actions + The following is an implementation of entry actions using internal events with content enter - utilizing a helper function enter/3 for state entry: + using a helper function enter/3 for state entry:

Example Revisited

- Here is the example after all mentioned modifications - and some more utilizing the entry actions, + This section includes the example after all mentioned modifications + and some more using the entry actions, which deserves a new state diagram:

- Code lock state diagram revisited + Code Lock State Diagram Revisited

- Note that this state diagram does not specify how to handle - a button event in the state open, so you will have to - read some other place that is here that unspecified events - shall be ignored as in not consumed but handled in some other state. - Nor does it show that the code_length/0 call shall be - handled in every state. + Notice that this state diagram does not specify how to handle + a button event in the state open. So, you need to + read somewhere else that unspecified events + must be ignored as in not consumed but handled in some other state. + Also, the state diagram does not show that the code_length/0 + call must be handled in every state.

@@ -1147,10 +1148,11 @@ code_change(_Vsn, State, Data, _Extra) ->
Callback Mode: handle_event_function

- What to change to use one handle_event/4 function. - Here a clean first-dispatch-on-event approach - does not work that well due to the generated - entry actions: + This section describes what to change in the example + to use one handle_event/4 function. + The following clean first-dispatch-on-event approach + does not work that well because of the generated + entry actions:

]]>

- Note that postponing buttons from the locked state + Notice that postponing buttons from the locked state to the open state feels like the wrong thing to do for a code lock, but it at least illustrates event postponing.

@@ -1206,33 +1208,33 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
Filter the State

- The example servers so far in this chapter will for example - when killed by an exit signal or due to an internal error - print out the full internal state in the error log. + The example servers so far in this chapter + print the full internal state in the error log, for example, + when killed by an exit signal or because of an internal error. This state contains both the code lock code - and which digits that remains to unlock. + and which digits that remain to unlock.

This state data can be regarded as sensitive, and maybe not what you want in the error log - because of something unpredictable happening. + because of some unpredictable event.

Another reason to filter the state can be - that the state is too big to print out since it fills + that the state is too large to print, as it fills the error log with uninteresting details.

- To avoid this you can format the internal state + To avoid this, you can format the internal state that gets in the error log and gets returned from sys:get_status/1,2 - by implementing the + by implementing function Module:format_status/2 - - function, for example like this: + , + for example like this:

Module:format_status/2 - function. If you do not a default implementation is used that + function. If you do not, a default implementation is used that does the same as this example function without filtering - the Data term that is: StateData = {State,Data}. + the Data term, that is, StateData = {State,Data}.

@@ -1275,54 +1277,56 @@ format_status(Opt, [_PDict,State,Data]) -> handle_event_function - enables using a non-atom state as described in + enables using a non-atom state as described in section - Callback Modes, - - for example a complex state term like a tuple. + Callback Modes + , + for example, a complex state term like a tuple.

One reason to use this is when you have - a state item that affects the event handling - in particular when combining that with postponing events. - Let us complicate the previous example + a state item that affects the event handling, + in particular in combination with postponing events. + We complicate the previous example by introducing a configurable lock button - (this is the state item in question) - that in the open state immediately locks the door, + (this is the state item in question), + which in the open state immediately locks the door, and an API function set_lock_button/1 to set the lock button.

Suppose now that we call set_lock_button while the door is open, and have already postponed a button event - that up until now was not the lock button; - the sensible thing might be to say that - the button was pressed too early so it should - not be recognized as the lock button, - but then it might be surprising that a button event + that until now was not the lock button. + The sensible thing can be to say that + the button was pressed too early so it is + not to be recognized as the lock button. + However, then it can be surprising that a button event that now is the lock button event arrives (as retried postponed) immediately after the state transits to locked.

- So let us make the button/1 function synchronous - by using gen_statem:call, + So we make the button/1 function synchronous + by using + + gen_statem:call and still postpone its events in the open state. Then a call to button/1 during the open - state will not return until the state transits to locked - since it is there the event is handled and the reply is sent. + state does not return until the state transits to locked, + as it is there the event is handled and the reply is sent.

- If now one process calls set_lock_button/1 - to change the lock button while some other process - hangs in button/1 with the new lock button - it could be expected that the hanging lock button call + If a process now calls set_lock_button/1 + to change the lock button while another process + hangs in button/1 with the new lock button, + it can be expected that the hanging lock button call immediately takes effect and locks the lock. - Therefore we make the current lock button a part of the state - so when we change the lock button the state will change - and all postponed events will be retried. + Therefore, we make the current lock button a part of the state, + so that when we change the lock button, the state changes + and all postponed events are retried.

- We define the state as {StateName,LockButton} + We define the state as {StateName,LockButton}, where StateName is as before and LockButton is the current lock button:

@@ -1441,10 +1445,9 @@ format_status(Opt, [_PDict,State,Data]) -> end. ]]>

- It may be an ill-fitting model for a physical code lock - that the button/1 call might hang until the lock - is locked. But for an API in general it is really not - that strange. + It can be an ill-fitting model for a physical code lock + that the button/1 call can hang until the lock + is locked. But for an API in general it is not that strange.

@@ -1457,26 +1460,25 @@ format_status(Opt, [_PDict,State,Data]) -> and they have some state(s) in their lifetime in which the servers can be expected to idle for a while, and the amount of heap memory all these servers need - is a problem; then it is possible to minimize - the memory footprint of a server by hibernating it through + is a problem, then the memory footprint of a server + can be mimimized by hibernating it through proc_lib:hibernate/3.

- To hibernate a process is rather costly. See - - erlang:hibernate/3. - - It is in general not something you want to do - after every event. + It is rather costly to hibernate a process; see + + erlang:hibernate/3 + . + It is not something you want to do after every event.

- We can in this example hibernate in the {open,_} state - since what normally happens in that state is that - the state timeout after a while + We can in this example hibernate in the {open,_} state, + because what normally occurs in that state is that + the state time-out after a while triggers a transition to {locked,_}:

action list on the last line when entering the {open,_} state is the only change. - If any event arrives in the {open,_}, state we - do not bother to re-hibernate, so the server stays + If any event arrives in the {open,_}, state, we + do not bother to rehibernate, so the server stays awake after any event.

To change that we would need to insert - the hibernate action in more places, - for example for the state independent set_lock_button + action hibernate in more places. + For example, for the state-independent set_lock_button and code_length operations that then would have to be aware of using hibernate while in the - {open,_} state which would clutter the code. + {open,_} state, which would clutter the code.

- This server probably does not use an amount of + This server probably does not use heap memory worth hibernating for. - To gain anything from hibernation your server would - have to actually produce some garbage during callback execution, - for which this example server may serve as a bad example. + To gain anything from hibernation, your server would + have to produce some garbage during callback execution, + for which this example server can serve as a bad example.

-- cgit v1.2.3 From 17405463ba134e71ff09e8d2921de9aa931805ee Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Mon, 9 May 2016 14:20:03 +0200 Subject: Fix all seealso and other minor changes --- system/doc/design_principles/statem.xml | 184 ++++++++++---------------------- 1 file changed, 57 insertions(+), 127 deletions(-) (limited to 'system/doc/design_principles/statem.xml') diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 8b0fbed7c0..585b1a35f5 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -84,8 +84,9 @@ State(S) x Event(E) -> Actions(A), State(S')
a server Data besides the state. Because of this, and as there is no restriction on the number of states (assuming that there is enough virtual machine memory) - or on the number of distinct input events, makes - a state machine implemented with this behavior Turing complete. + or on the number of distinct input events, + a state machine implemented with this behavior + is in fact Turing complete. But it feels mostly like an Event-Driven Mealy Machine.

@@ -101,8 +102,8 @@ State(S) x Event(E) -> Actions(A), State(S')

- In mode - state_functions, + In mode + state_functions, the state transition rules are written as some Erlang functions, which conform to the following convention:

@@ -113,20 +114,19 @@ StateName(EventType, EventContent, Data) ->

- In mode - handle_event_function, + In mode + handle_event_function, only one Erlang function provides all state transition rules:

 handle_event(EventType, EventContent, State, Data) ->
     .. code for actions here ...
-    {next_state, State', Data'}
+ {next_state, NewState, NewData}

Both these modes allow other return tuples; see - - Module:StateName/3 + Module:StateName/3 in the gen_statem manual page. These other return tuples can, for example, stop the machine, execute state transition actions on the machine engine itself, @@ -172,8 +172,7 @@ handle_event(EventType, EventContent, State, Data) -> This mode works equally well when you want to focus on one event at the time or on one state at the time, but function - - Module:handle_event/4 + Module:handle_event/4 quickly grows too large to handle without introducing dispatching.

@@ -291,9 +290,7 @@ start_link(Code) -> ]]>

start_link calls function - - gen_statem:start_link/4 - , + gen_statem:start_link/4, which spawns and links to a new process, a gen_statem.

@@ -308,9 +305,7 @@ start_link(Code) -> Instead its pid must be used. The name can also be specified as {global,Name}, then the gen_statem is registered using - - global:register_name/2 - + global:register_name/2 in Kernel.

@@ -339,9 +334,7 @@ start_link(Code) ->

The fourth argument, [], is a list of options. For the available options, see - - gen_statem:start_link/3 - . + gen_statem:start_link/3.

@@ -350,13 +343,9 @@ start_link(Code) -> calls callback function code_lock:init(Code). This function is expected to return {CallbackMode,State,Data}, where - - CallbackMode - + CallbackMode selects callback module state function mode, in this case - - state_functions - + state_functions through macro ?CALLBACK_MODE. That is, each state has got its own handler function. State is the initial state of the gen_statem, @@ -375,23 +364,17 @@ init(Code) -> {?CALLBACK_MODE,locked,Data}. ]]>

Function - - gen_statem:start_link - + gen_statem:start_link is synchronous. It does not return until the gen_statem is initialized and is ready to receive events.

Function - - gen_statem:start_link - + gen_statem:start_link must be used if the gen_statem is part of a supervision tree, that is, started by a supervisor. Another function, - - gen_statem:start - + gen_statem:start can be used to start a standalone gen_statem, that is, a gen_statem that is not part of a supervision tree.

@@ -403,9 +386,7 @@ init(Code) -> Handling Events

The function notifying the code lock about a button event is implemented using - - gen_statem:cast/2: - + gen_statem:cast/2:

@@ -528,9 +509,7 @@ handle_event({call,From}, code_length, #{code := Code} = Data) -> ]]>

This example uses - - gen_statem:call/2 - , + gen_statem:call/2, which waits for a reply from the server. The reply is sent with a {reply,From,Reply} tuple in an action list in the {keep_state,...} tuple @@ -545,15 +524,13 @@ handle_event({call,From}, code_length, #{code := Code} = Data) ->

If mode handle_event_function is used, all events are handled in - - Module:handle_event/4 - + Module:handle_event/4 and we can (but do not have to) use an event-centered approach where we dispatch on event first and then state:

strategy must be a time-out value and the gen_statem must in function init/1 set itself to trap exit signals by calling - - process_flag(trap_exit, true) - . - When ordered to shut down, the gen_statem then calls - callback function terminate(shutdown, State, Data): + process_flag(trap_exit, true):

@@ -616,6 +589,10 @@ init(Args) -> do_lock(), ... ]]> +

+ When ordered to shut down, the gen_statem then calls + callback function terminate(shutdown, State, Data). +

In the following example, function terminate/3 locks the door if it is open, so we do not accidentally leave the door @@ -633,9 +610,7 @@ terminate(_Reason, State, _Data) ->

If the gen_statem is not part of a supervision tree, it can be stopped using - - gen_statem:stop - , + gen_statem:stop, preferably through an API function:

Actions

In the first sections actions were mentioned as a part of - the general state machine model. These actions + the general state machine model. These general actions are implemented with the code that callback module gen_statem executes in an event-handling callback function before returning @@ -671,17 +646,11 @@ stop() -> that a callback function can order the gen_statem engine to do after the callback function return. These are ordered by returning a list of - - actions - + actions in the - - return tuple - + return tuple from the - - callback function - . + callback function. These state transition actions affect the gen_statem engine itself and can do the following:

@@ -697,9 +666,7 @@ stop() -> and replying to a caller. An example of event postponing is included later in this chapter. For details, see the - - gen_statem(3) - + gen_statem(3) manual page. You can, for example, reply to many callers and generate multiple next events to handle. @@ -712,9 +679,7 @@ stop() -> Event Types

The previous sections mentioned a few - - event types - . + event types. Events of all types are handled in the same callback function, for a given state, and the function gets EventType and EventContent as arguments. @@ -727,22 +692,16 @@ stop() -> cast Generated by - - gen_statem:cast. - + gen_statem:cast. {call,From} Generated by - - gen_statem:call - , + gen_statem:call, where From is the reply address to use when replying either through the state transition action {reply,From,Msg} or by calling - - gen_statem:reply - . + gen_statem:reply. info @@ -759,7 +718,7 @@ stop() -> Generated by state transition action {next_event,internal,EventContent}. - All event types above can be generated using + All event types above can also be generated using {next_event,EventType,EventContent}. @@ -780,9 +739,7 @@ stop() -> or you want to start a timer in one state and respond to the time-out in another. This can be accomplished with a regular Erlang timer: - - erlang:start_timer. - + erlang:start_timer.

For the example so far in this chapter: using the @@ -818,9 +775,7 @@ open(cast, {button,_}, Data) -> ]]>

If you need to cancel a timer because of some other event, you can use - - erlang:cancel_timer(Tref) - . + erlang:cancel_timer(Tref). Notice that a time-out message cannot arrive after this, unless you have postponed it (see the next section) before, so ensure that you do not accidentally postpone such messages. @@ -844,9 +799,7 @@ open(cast, {button,_}, Data) ->

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

@@ -861,7 +814,7 @@ open(cast, {button,_}, Data) -> ... ]]>

- A postponed event is only retried after a state change + The fact that a postponed event is only retried after a state change translates into a requirement on the event and state space. If you have a choice between storing a state data item in the State or in the Data: @@ -953,9 +906,7 @@ do_unlock() ->

The state transition - - action - + action postpone is designed to model selective receives. A selective receive implicitly postpones any not received events, but the postpone @@ -977,16 +928,12 @@ do_unlock() -> 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 - + action {next_event,EventType,EventContent}.

You can generate events of any existing - - type - , + type, but the internal type can only be generated through action next_event. Hence, it cannot come from an external source, so you can be certain that an internal event is an event @@ -1150,9 +1097,9 @@ code_change(_Vsn, State, Data, _Extra) ->

This section describes what to change in the example to use one handle_event/4 function. - The following clean first-dispatch-on-event approach - does not work that well because of the generated - entry actions: + The previously used clean first-dispatch-on-event approach + does not work that well here because of the generated + entry actions so this example dispatches on state first:

To avoid this, you can format the internal state that gets in the error log and gets returned from - - sys:get_status/1,2 - + sys:get_status/1,2 by implementing function - - Module:format_status/2 - , + Module:format_status/2, for example like this:

]]>

It is not mandatory to implement a - - Module:format_status/2 - + Module:format_status/2 function. If you do not, a default implementation is used that does the same as this example function without filtering the Data term, that is, StateData = {State,Data}. @@ -1274,13 +1215,9 @@ format_status(Opt, [_PDict,State,Data]) -> Complex State

The callback mode - - handle_event_function - + handle_event_function enables using a non-atom state as described in section - - Callback Modes - , + Callback Modes, for example, a complex state term like a tuple.

@@ -1308,8 +1245,7 @@ format_status(Opt, [_PDict,State,Data]) ->

So we make the button/1 function synchronous by using - - gen_statem:call + gen_statem:call and still postpone its events in the open state. Then a call to button/1 during the open state does not return until the state transits to locked, @@ -1462,16 +1398,12 @@ format_status(Opt, [_PDict,State,Data]) -> and the amount of heap memory all these servers need is a problem, then the memory footprint of a server can be mimimized by hibernating it through - - proc_lib:hibernate/3. - + proc_lib:hibernate/3.

It is rather costly to hibernate a process; see - - erlang:hibernate/3 - . + erlang:hibernate/3. It is not something you want to do after every event.

@@ -1495,9 +1427,7 @@ handle_event( ]]>

The - - [hibernate] - + [hibernate] action list on the last line when entering the {open,_} state is the only change. If any event arrives in the {open,_}, state, we -- cgit v1.2.3