diff options
Diffstat (limited to 'system')
-rw-r--r-- | system/doc/design_principles/statem.xml | 776 |
1 files changed, 354 insertions, 422 deletions
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index ca0fce55e2..585b1a35f5 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -5,7 +5,7 @@ <header> <copyright> <year>2016</year> - <holder>Ericsson AB. All Rights Reserved.</holder> + <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ </legalnotice> - <title>gen_statem Behaviour</title> + <title>gen_statem Behavior</title> <prepared></prepared> <docno></docno> <date></date> @@ -33,63 +33,61 @@ <p> This section is to be read with the <seealso marker="stdlib:gen_statem"><c>gen_statem(3)</c></seealso> - manual page in STDLIB, where all interface functions and callback + manual page in <c>STDLIB</c>, where all interface functions and callback functions are described in detail. </p> - <p> - 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"... - </p> + <note> + <p> + 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. + </p> + </note> <!-- =================================================================== --> <section> - <title>Event Driven State Machines</title> + <title>Event-Driven State Machines</title> <p> 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. </p> <p> - 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: </p> <pre> State(S) x Event(E) -> Actions(A), State(S')</pre> - <p>These relations are interpreted as meaning:</p> - <p> - If we are in state <c>S</c> and event <c>E</c> occurs, we + <p>These relations are interpreted as follows: + if we are in state <c>S</c> and event <c>E</c> occurs, we are to perform actions <c>A</c> and make a transition to - state <c>S'</c>. - </p> - <p> - Note that <c>S'</c> may be equal to <c>S</c>. + state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>. </p> <p> - Since <c>A</c> and <c>S'</c> depend only on - <c>S</c> and <c>E</c> the kind of state machine described - here is a Mealy Machine. - (See for example the corresponding Wikipedia article) + As <c>A</c> and <c>S'</c> depend only on + <c>S</c> and <c>E</c>, the kind of state machine described + here is a Mealy Machine + (see, for example, the corresponding Wikipedia article). </p> <p> - Like most <c>gen_</c> behaviours, <c>gen_statem</c> keeps - a server <c>Data</c> besides the state. This and the fact that + Like most <c>gen_</c> behaviors, <c>gen_statem</c> keeps + a server <c>Data</c> 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, + a state machine implemented with this behavior + is in fact Turing complete. + But it feels mostly like an Event-Driven Mealy Machine. </p> </section> @@ -99,38 +97,39 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <marker id="callback_modes" /> <title>Callback Modes</title> <p> - The <c>gen_statem</c> behaviour supports two different callback modes. - In the mode - <seealso marker="stdlib:gen_statem#type-callback_mode"> - <c>state_functions</c>, - </seealso> - the state transition rules are written as a number of Erlang - functions, which conform to the following convention: + The <c>gen_statem</c> behavior supports two callback modes: </p> - <pre> + <list type="bulleted"> + <item> + <p> + In mode + <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>, + the state transition rules are written as some Erlang + functions, which conform to the following convention: + </p> + <pre> StateName(EventType, EventContent, Data) -> .. code for actions here ... {next_state, NewStateName, NewData}.</pre> - <p> - In the mode - <seealso marker="stdlib:gen_statem#type-callback_mode"> - <c>handle_event_function</c> - </seealso> - there is only one - Erlang function that implements all state transition rules: - </p> - <pre> + </item> + <item> + <p> + In mode + <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>, + only one Erlang function provides all state transition rules: + </p> + <pre> handle_event(EventType, EventContent, State, Data) -> .. code for actions here ... - {next_state, State', Data'}</pre> - <p> - Both these modes allow other return tuples - that you can find in the - <seealso marker="stdlib:gen_statem#Module:StateName/3"> - reference manual. - </seealso> - These other return tuples can for example stop the machine, - execute state transition actions on the machine engine itself + {next_state, NewState, NewData}</pre> + </item> + </list> + <p> + Both these modes allow other return tuples; see + <seealso marker="stdlib:gen_statem#Module:StateName/3"><c>Module:StateName/3</c></seealso> + in the <c>gen_statem</c> manual page. + These other return tuples can, for example, stop the machine, + execute state transition actions on the machine engine itself, and send replies. </p> @@ -139,54 +138,54 @@ handle_event(EventType, EventContent, State, Data) -> <p> The two <seealso marker="#callback_modes">callback modes</seealso> - gives different possibilities + give different possibilities and restrictions, but one goal remains: you want to handle all possible combinations of events and states. </p> <p> - 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. </p> <p> - With <c>state_functions</c> you are restricted to use - atom only states, and the <c>gen_statem</c> engine dispatches - on state name for you. This encourages the callback module + With <c>state_functions</c>, you are restricted to use + atom-only states, and the <c>gen_statem</c> 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. </p> <p> - 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. </p> <p> - With <c>handle_event_function</c> you are free to mix strategies - as you like because all events and states - are handled in the the same callback function. + With <c>handle_event_function</c>, you are free to mix strategies, + as all events and states are handled in the same callback function. </p> <p> 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 <c>handle_event/4</c> function + one event at the time or on + one state at the time, but function + <seealso marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seealso> quickly grows too large to handle without introducing dispatching. </p> <p> - 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 <c>{StateName,server}</c> or - <c>{StateName,client}</c> and since you do the dispatching - yourself you make <c>StateName</c> decide where in the code + for the client side and the server side of a protocol, + you can have a state <c>{StateName,server}</c> or + <c>{StateName,client}</c>. Also, as you do the dispatching + yourself, you make <c>StateName</c> 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. </p> </section> </section> @@ -196,31 +195,28 @@ handle_event(EventType, EventContent, State, Data) -> <section> <title>Example</title> <p> - This is an example starting off as equivalent to the the example in the - <seealso marker="fsm"><c>gen_fsm</c> behaviour</seealso> - description. In later chapters additions and tweaks are made + This example starts off as equivalent to the example in section + <seealso marker="fsm"><c>gen_fsm</c> Behavior</seealso>. + In later sections, additions and tweaks are made using features in <c>gen_statem</c> that <c>gen_fsm</c> 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. </p> <p> - 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. - </p> - <p> - 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. </p> <image file="../design_principles/code_lock.png"> - <icaption>Code lock state diagram</icaption> + <icaption>Code Lock State Diagram</icaption> </image> <p> - We can implement such a code lock state machine using + This code lock state machine can be implemented using <c>gen_statem</c> with the following callback module: </p> <marker id="ex"></marker> @@ -241,7 +237,6 @@ start_link(Code) -> button(Digit) -> gen_statem:cast(?NAME, {button,Digit}). - init(Code) -> do_lock(), Data = #{code => Code, remaining => Code}, @@ -286,7 +281,7 @@ code_change(_Vsn, State, Data, _Extra) -> <section> <title>Starting gen_statem</title> <p> - In the example in the previous section, the <c>gen_statem</c> is + In the example in the previous section, <c>gen_statem</c> is started by calling <c>code_lock:start_link(Code)</c>: </p> <code type="erl"><![CDATA[ @@ -294,80 +289,71 @@ start_link(Code) -> gen_statem:start_link({local,?NAME}, ?MODULE, Code, []). ]]></code> <p> - <c>start_link</c> calls the function - <seealso marker="stdlib:gen_statem#start_link/4"> - <c>gen_statem:start_link/4</c> - </seealso> - which spawns and links to a new process; a <c>gen_statem</c>. + <c>start_link</c> calls function + <seealso marker="stdlib:gen_statem#start_link/4"><c>gen_statem:start_link/4</c></seealso>, + which spawns and links to a new process, a <c>gen_statem</c>. </p> <list type="bulleted"> <item> <p> - The first argument, <c>{local,?NAME}</c>, specifies - the name. In this case, the <c>gen_statem</c> is locally - registered as <c>code_lock</c> through the macro <c>?NAME</c>. - </p> + The first argument, <c>{local,?NAME}</c>, specifies + the name. In this case, the <c>gen_statem</c> is locally + registered as <c>code_lock</c> through the macro <c>?NAME</c>. + </p> <p> - If the name is omitted, the <c>gen_statem</c> is not registered. - Instead its pid must be used. The name can also be given - as <c>{global,Name}</c>, in which case the <c>gen_statem</c> is - registered using - <seealso marker="kernel:global#register_name/2"> - <c>global:register_name/2</c>. - </seealso> - </p> + If the name is omitted, the <c>gen_statem</c> is not registered. + Instead its pid must be used. The name can also be specified + as <c>{global,Name}</c>, then the <c>gen_statem</c> is + registered using + <seealso marker="kernel:global#register_name/2"><c>global:register_name/2</c></seealso> + in <c>Kernel</c>. + </p> </item> <item> <p> - The second argument, <c>?MODULE</c>, is the name of - the callback module, that is; the module where the callback + The second argument, <c>?MODULE</c>, is the name of + the callback module, that is, the module where the callback functions are located, which is this module. - </p> + </p> <p> - The interface functions (<c>start_link/1</c> and <c>button/1</c>) - are located in the same module as the callback functions - (<c>init/1</c>, <c>locked/3</c>, and <c>open/3</c>). - It is normally good programming practice to have the client - side and the server side code contained in one module. - </p> + The interface functions (<c>start_link/1</c> and <c>button/1</c>) + are located in the same module as the callback functions + (<c>init/1</c>, <c>locked/3</c>, and <c>open/3</c>). + It is normally good programming practice to have the client-side + code and the server-side code contained in one module. + </p> </item> <item> <p> - The third argument, <c>Code</c>, is a list of digits that - is the correct unlock code which is passsed - to the callback function <c>init/1</c>. + The third argument, <c>Code</c>, is a list of digits, which + is the correct unlock code that is passed + to callback function <c>init/1</c>. </p> </item> <item> <p> - The fourth argument, <c>[]</c>, is a list of options. See the - <seealso marker="stdlib:gen_statem#start_link/3"> - <c>gen_statem:start_link/3</c> - </seealso> - manual page for available options. + The fourth argument, <c>[]</c>, is a list of options. + For the available options, see + <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link/3</c></seealso>. </p> </item> </list> <p> If name registration succeeds, the new <c>gen_statem</c> process - calls the callback function <c>code_lock:init(Code)</c>. + calls callback function <c>code_lock:init(Code)</c>. This function is expected to return <c>{CallbackMode,State,Data}</c>, where - <seealso marker="#callback_modes"> - <c>CallbackMode</c> - </seealso> + <seealso marker="#callback_modes"><c>CallbackMode</c></seealso> selects callback module state function mode, in this case - <seealso marker="stdlib:gen_statem#type-callback_mode"> - <c>state_functions</c> - </seealso> - through the macro <c>?CALLBACK_MODE</c> that is; each state + <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso> + through macro <c>?CALLBACK_MODE</c>. That is, each state has got its own handler function. <c>State</c> is the initial state of the <c>gen_statem</c>, - in this case <c>locked</c>; assuming the door is locked to begin with. - <c>Data</c> is the internal server data of the <c>gen_statem</c>. + in this case <c>locked</c>; assuming that the door is locked to begin + with. <c>Data</c> is the internal server data of the <c>gen_statem</c>. Here the server data is a <seealso marker="stdlib:maps">map</seealso> - with the key <c>code</c> that stores - the correct button sequence and the key <c>remaining</c> + with key <c>code</c> that stores + the correct button sequence, and key <c>remaining</c> that stores the remaining correct button sequence (the same as the <c>code</c> to begin with). </p> @@ -377,24 +363,19 @@ init(Code) -> Data = #{code => Code, remaining => Code}, {?CALLBACK_MODE,locked,Data}. ]]></code> - <p> - <seealso marker="stdlib:gen_statem#start_link/3"> - <c>gen_statem:start_link</c> - </seealso> - is synchronous. It does not return until the <c>gen_statem</c> - has been initialized and is ready to receive events. + <p>Function + <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso> + is synchronous. It does not return until the <c>gen_statem</c> + is initialized and is ready to receive events. </p> <p> - <seealso marker="stdlib:gen_statem#start_link/3"> - <c>gen_statem:start_link</c> - </seealso> + Function + <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso> must be used if the <c>gen_statem</c> - is part of a supervision tree, that is; started by a supervisor. - There is another function; - <seealso marker="stdlib:gen_statem#start/3"> - <c>gen_statem:start</c> - </seealso> - to start a standalone <c>gen_statem</c>, that is; + is part of a supervision tree, that is, started by a supervisor. + Another function, + <seealso marker="stdlib:gen_statem#start/3"><c>gen_statem:start</c></seealso> + can be used to start a standalone <c>gen_statem</c>, that is, a <c>gen_statem</c> that is not part of a supervision tree. </p> </section> @@ -402,12 +383,10 @@ init(Code) -> <!-- =================================================================== --> <section> - <title>Events and Handling them</title> + <title>Handling Events</title> <p>The function notifying the code lock about a button event is implemented using - <seealso marker="stdlib:gen_statem#cast/2"> - <c>gen_statem:cast/2</c>: - </seealso> + <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast/2</c></seealso>: </p> <code type="erl"><![CDATA[ button(Digit) -> @@ -415,9 +394,9 @@ button(Digit) -> ]]></code> <p> The first argument is the name of the <c>gen_statem</c> 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 <c>?NAME</c> as when starting. - <c>{button,Digit}</c> is the actual event content. + <c>{button,Digit}</c> is the event content. </p> <p> The event is made into a message and sent to the <c>gen_statem</c>. @@ -452,19 +431,19 @@ open(cast, {button,_}, Data) -> ]]></code> <p> 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 <c>gen_statem</c> goes to state <c>open</c>, or the door remains in state <c>locked</c>. </p> <p> - 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. </p> <p> - In state <c>open</c> 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 <c>open</c>, any button locks the door, as + any event cancels the event timer, so no + time-out event occurs after a button event. </p> </section> @@ -478,11 +457,11 @@ open(cast, {button,_}, Data) -> {next_state,open,Data#{remaining := Code},10000}; ]]></code> <p> - 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, <c>StateName(timeout, 10000, Data)</c> is called. The time-out occurs when the door has been in state <c>open</c> - for 10 seconds. After that the door is locked again: + for 10 seconds. After that the door is locked again: </p> <code type="erl"><![CDATA[ open(timeout, _, Data) -> @@ -496,16 +475,16 @@ open(timeout, _, Data) -> <section> <title>All State Events</title> <p> - Sometimes an event can arrive in any state of the <c>gen_statem</c>. + Sometimes events can arrive in any state of the <c>gen_statem</c>. It is convenient to handle these in a common state handler function that all state functions call for events not specific to the state. </p> <p> - Let's introduce a <c>code_length/0</c> function that returns + Consider a <c>code_length/0</c> 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 <c>handle_event/3</c>. + (that should not be sensitive to reveal). + We dispatch all events that are not state-specific + to the common function <c>handle_event/3</c>: </p> <code type="erl"><![CDATA[ ... @@ -530,9 +509,7 @@ handle_event({call,From}, code_length, #{code := Code} = Data) -> ]]></code> <p> This example uses - <seealso marker="stdlib:gen_statem#call/2"> - <c>gen_statem:call/2</c> - </seealso> + <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call/2</c></seealso>, which waits for a reply from the server. The reply is sent with a <c>{reply,From,Reply}</c> tuple in an action list in the <c>{keep_state,...}</c> tuple @@ -545,14 +522,15 @@ handle_event({call,From}, code_length, #{code := Code} = Data) -> <section> <title>One Event Handler</title> <p> - If you use the mode <c>handle_event_function</c> - all events are handled in <c>handle_event/4</c> and we - may (but do not have to) use an event-centered approach + If mode <c>handle_event_function</c> is used, + all events are handled in + <seealso marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seealso> + and we can (but do not have to) use an event-centered approach where we dispatch on event first and then state: </p> <code type="erl"><![CDATA[ ... --define(CALLBACK_MODE, state_functions). +-define(CALLBACK_MODE, handle_event_function). ... -export([handle_event/4]). @@ -596,19 +574,14 @@ handle_event(timeout, _, open, Data) -> The <c>gen_statem</c> is automatically terminated by its supervisor. Exactly how this is done is defined by a <seealso marker="sup_princ#shutdown">shutdown strategy</seealso> - set in the supervisor. + set in the supervisor. </p> <p> If it is necessary to clean up before termination, the shutdown - strategy must be a time-out value and the <c>gen_statem</c> must - in the <c>init/1</c> function set itself to trap exit signals + strategy must be a time-out value and the <c>gen_statem</c> must + in function <c>init/1</c> set itself to trap exit signals by calling - <seealso marker="erts:erlang#process_flag/2"> - <c>process_flag(trap_exit, true)</c>. - </seealso> - When ordered to shutdown, the <c>gen_statem</c> then calls - the callback function - <c>terminate(shutdown, State, Data)</c>: + <seealso marker="erts:erlang#process_flag/2"><c>process_flag(trap_exit, true)</c></seealso>: </p> <code type="erl"><![CDATA[ init(Args) -> @@ -617,9 +590,13 @@ init(Args) -> ... ]]></code> <p> - In this example we let the <c>terminate/3</c> function - lock the door if it is open so we do not accidentally leave the door - open when the supervision tree terminates. + When ordered to shut down, the <c>gen_statem</c> then calls + callback function <c>terminate(shutdown, State, Data)</c>. + </p> + <p> + In the following example, function <c>terminate/3</c> + locks the door if it is open, so we do not accidentally leave the door + open when the supervision tree terminates: </p> <code type="erl"><![CDATA[ terminate(_Reason, State, _Data) -> @@ -633,9 +610,7 @@ terminate(_Reason, State, _Data) -> <p> If the <c>gen_statem</c> is not part of a supervision tree, it can be stopped using - <seealso marker="stdlib:gen_statem#stop/1"> - <c>gen_statem:stop</c>, - </seealso> + <seealso marker="stdlib:gen_statem#stop/1"><c>gen_statem:stop</c></seealso>, preferably through an API function: </p> <code type="erl"><![CDATA[ @@ -647,8 +622,8 @@ stop() -> gen_statem:stop(?NAME). ]]></code> <p> - This makes the <c>gen_statem</c> call the <c>terminate/3</c> - callback function just like for a supervised server + This makes the <c>gen_statem</c> call callback function + <c>terminate/3</c> just like for a supervised server and waits for the process to terminate. </p> </section> @@ -659,48 +634,41 @@ stop() -> <section> <title>Actions</title> <p> - 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 <c>gen_statem</c> - callback module executes in an event handling + In the first sections actions were mentioned as a part of + the general state machine model. These general actions + are implemented with the code that callback module + <c>gen_statem</c> executes in an event-handling callback function before returning to the <c>gen_statem</c> engine. </p> <p> - There are more specific state transition actions + There are more specific state-transition actions that a callback function can order the <c>gen_statem</c> engine to do after the callback function return. These are ordered by returning a list of - <seealso marker="stdlib:gen_statem#type-action"> - actions - </seealso> + <seealso marker="stdlib:gen_statem#type-action">actions</seealso> in the - <seealso marker="stdlib:gen_statem#type-state_function_result"> - return tuple - </seealso> + <seealso marker="stdlib:gen_statem#type-state_function_result">return tuple</seealso> from the - <seealso marker="stdlib:gen_statem#Module:StateName/3"> - callback function. - </seealso> + <seealso marker="stdlib:gen_statem#Module:StateName/3">callback function</seealso>. These state transition actions affect the <c>gen_statem</c> - engine itself. They can: + engine itself and can do the following: </p> <list type="bulleted"> - <item>Postpone the current event.</item> - <item>Hibernate the <c>gen_statem</c>.</item> - <item>Start an event timeout.</item> - <item>Reply to a caller.</item> - <item>Generate the next event to handle.</item> + <item>Postpone the current event</item> + <item>Hibernate the <c>gen_statem</c></item> + <item>Start an event time-out</item> + <item>Reply to a caller</item> + <item>Generate the next event to handle</item> </list> <p> - 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 - <seealso marker="stdlib:gen_statem#type-action"> - reference manual - </seealso> - for details. You can for example actually reply to several callers + 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 + <seealso marker="stdlib:gen_statem#type-action"><c>gen_statem(3)</c></seealso> + manual page. + You can, for example, reply to many callers and generate multiple next events to handle. </p> </section> @@ -710,38 +678,30 @@ stop() -> <section> <title>Event Types</title> <p> - So far we have mentioned a few - <seealso marker="stdlib:gen_statem#type-event_type"> - event types. - </seealso> + The previous sections mentioned a few + <seealso marker="stdlib:gen_statem#type-event_type">event types</seealso>. Events of all types are handled in the same callback function, for a given state, and the function gets <c>EventType</c> and <c>EventContent</c> as arguments. </p> <p> - Here is the complete list of event types and where + The following is a complete list of event types and where they come from: </p> <taglist> <tag><c>cast</c></tag> <item> Generated by - <seealso marker="stdlib:gen_statem#cast/2"> - <c>gen_statem:cast</c>. - </seealso> + <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast</c></seealso>. </item> <tag><c>{call,From}</c></tag> <item> Generated by - <seealso marker="stdlib:gen_statem#call/2"> - <c>gen_statem:call</c> - </seealso> + <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>, where <c>From</c> is the reply address to use when replying either through the state transition action <c>{reply,From,Msg}</c> or by calling - <seealso marker="stdlib:gen_statem#reply/1"> - <c>gen_statem:reply</c>. - </seealso> + <seealso marker="stdlib:gen_statem#reply/1"><c>gen_statem:reply</c></seealso>. </item> <tag><c>info</c></tag> <item> @@ -750,15 +710,15 @@ stop() -> </item> <tag><c>timeout</c></tag> <item> - Generated by the state transition action + Generated by state transition action <c>{timeout,Time,EventContent}</c> (or its short form <c>Time</c>) timer timing out. </item> <tag><c>internal</c></tag> <item> - Generated by the state transition action + Generated by state transition action <c>{next_event,internal,EventContent}</c>. - In fact all event types above can be generated using + All event types above can also be generated using <c>{next_event,EventType,EventContent}</c>. </item> </taglist> @@ -767,34 +727,32 @@ stop() -> <!-- =================================================================== --> <section> - <title>State Timeouts</title> + <title>State Time-Outs</title> <p> - The timeout event generated by the state transition action - <c>{timeout,Time,EventContent}</c> 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 + <c>{timeout,Time,EventContent}</c> 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. </p> <p> - 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: - <seealso marker="erts:erlang#start_timer/4"> - <c>erlang:start_timer</c>. - </seealso> + to the time-out in another. This can be accomplished + with a regular Erlang timer: + <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer</c></seealso>. </p> <p> - Looking at the example in this chapter so far; using the + For the example so far in this chapter: using the <c>gen_statem</c> event timer has the consequence that if a button event is generated while in the <c>open</c> 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. </p> <p> - 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 <c>open</c> state. Then we start a timer when entering the <c>open</c> state - and wait for it to expire while ignoring button events: + and waits for it to expire while ignoring button events: </p> <code type="erl"><![CDATA[ ... @@ -816,17 +774,15 @@ open(cast, {button,_}, Data) -> ... ]]></code> <p> - If you need to cancel a timer due to some other event you can use - <seealso marker="erts:erlang#cancel_timer/2"> - <c>erlang:cancel_timer(Tref)</c>. - </seealso> - Note that a timeout message can not arrive after this, + If you need to cancel a timer because of some other event, you can use + <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>. + 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. </p> <p> - 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. </p> </section> @@ -839,19 +795,17 @@ 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 <c>OldState =/= NewState</c>. + changed, that is, <c>OldState =/= NewState</c>. </p> <p> Postponing is ordered by the state transition - <seealso marker="stdlib:gen_statem#type-action"> - action - </seealso> + <seealso marker="stdlib:gen_statem#type-action">action</seealso> <c>postpone</c>. </p> <p> In this example, instead of ignoring button events - while in the <c>open</c> state we can postpone them - and they will be queued and later handled in the <c>locked</c> state: + while in the <c>open</c> state, we can postpone them + and they are queued and later handled in the <c>locked</c> state: </p> <code type="erl"><![CDATA[ ... @@ -861,16 +815,16 @@ open(cast, {button,_}, Data) -> ]]></code> <p> 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 <c>State</c> or in the <c>Data</c>; - should a change in the item value affect which events that - are handled, then this item ought to be part of the state. + translates into a requirement on the event and state space. + If you have a choice between storing a state data item + in the <c>State</c> or in the <c>Data</c>: + if a change in the item value affects which events that + are handled, then this item is to be part of the state. </p> <p> - 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 <c>Data</c> but not the <c>State</c>. + 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 <c>Data</c> but not the <c>State</c>. </p> <section> @@ -883,7 +837,7 @@ open(cast, {button,_}, Data) -> or from the context. </p> <p> - 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. </p> @@ -892,10 +846,10 @@ open(cast, {button,_}, Data) -> <section> <title>Selective Receive</title> <p> - 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: </p> <code type="erl"><![CDATA[ -module(code_lock). @@ -937,33 +891,31 @@ do_unlock() -> io:format("Open~n", []). ]]></code> <p> - The selective receive in this case causes <c>open</c> - to implicitly postpone any events to the <c>locked</c> state. + The selective receive in this case causes implicitly <c>open</c> + to postpone any events to the <c>locked</c> state. </p> <p> - A selective receive can not be used from a <c>gen_statem</c> - behaviour just as for any <c>gen_*</c> behavior - since the receive statement is within the <c>gen_*</c> engine itself. - It has to be there because all + A selective receive cannot be used from a <c>gen_statem</c> + behavior as for any <c>gen_*</c> behavior, + as the receive statement is within the <c>gen_*</c> engine itself. + It must be there because all <seealso marker="stdlib:sys"><c>sys</c></seealso> - 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. </p> <p> The state transition - <seealso marker="stdlib:gen_statem#type-action"> - action - </seealso> - <c>postpone</c> is designed to be able to model - selective receives. A selective receive implicitly postpones + <seealso marker="stdlib:gen_statem#type-action">action</seealso> + <c>postpone</c> is designed to model + selective receives. A selective receive implicitly postpones any not received events, but the <c>postpone</c> state transition action explicitly postpones one received event. </p> <p> - 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. </p> </section> </section> @@ -971,43 +923,39 @@ do_unlock() -> <!-- =================================================================== --> <section> - <title>Self Generated Events</title> + <title>Self-Generated Events</title> <p> - 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 - <seealso marker="stdlib:gen_statem#type-action"> - action - </seealso> + <seealso marker="stdlib:gen_statem#type-action">action</seealso> <c>{next_event,EventType,EventContent}</c>. </p> <p> You can generate events of any existing - <seealso marker="stdlib:gen_statem#type-action"> - type, - </seealso> - but the <c>internal</c> type can only be generated through the - <c>next_event</c> action and hence can not come from an external source, + <seealso marker="stdlib:gen_statem#type-action">type</seealso>, + but the <c>internal</c> type can only be generated through action + <c>next_event</c>. Hence, it cannot come from an external source, so you can be certain that an <c>internal</c> event is an event from your state machine to itself. </p> <p> - 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 <c>internal</c> 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 <c>internal</c> event - or use state transition function. + or use a state transition function. </p> <p> - Here is an implementation of entry actions + The following is an implementation of entry actions using <c>internal</c> events with content <c>enter</c> - utilizing a helper function <c>enter/3</c> for state entry: + using a helper function <c>enter/3</c> for state entry: </p> <code type="erl"><![CDATA[ ... @@ -1051,20 +999,20 @@ enter(Tag, State, Data) -> <section> <title>Example Revisited</title> <p> - 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: </p> <image file="../design_principles/code_lock_2.png"> - <icaption>Code lock state diagram revisited</icaption> + <icaption>Code Lock State Diagram Revisited</icaption> </image> <p> - Note that this state diagram does not specify how to handle - a button event in the state <c>open</c>, 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 <c>code_length/0</c> call shall be - handled in every state. + Notice that this state diagram does not specify how to handle + a button event in the state <c>open</c>. 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 <c>code_length/0</c> + call must be handled in every state. </p> <section> @@ -1147,10 +1095,11 @@ code_change(_Vsn, State, Data, _Extra) -> <section> <title>Callback Mode: handle_event_function</title> <p> - What to change to use one <c>handle_event/4</c> 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 <c>handle_event/4</c> function. + 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: </p> <code type="erl"><![CDATA[ ... @@ -1195,7 +1144,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) -> ]]></code> </section> <p> - Note that postponing buttons from the <c>locked</c> state + Notice that postponing buttons from the <c>locked</c> state to the <c>open</c> state feels like the wrong thing to do for a code lock, but it at least illustrates event postponing. </p> @@ -1206,33 +1155,29 @@ handle_event({call,From}, code_length, _State, #{code := Code}) -> <section> <title>Filter the State</title> <p> - 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. </p> <p> 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. </p> <p> 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. </p> <p> - 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 - <seealso marker="stdlib:sys#get_status/1"> - <c>sys:get_status/1,2</c> - </seealso> - by implementing the - <seealso marker="stdlib:gen_statem#Module:format_status/2"> - <c>Module:format_status/2</c> - </seealso> - function, for example like this: + <seealso marker="stdlib:sys#get_status/1"><c>sys:get_status/1,2</c></seealso> + by implementing function + <seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso>, + for example like this: </p> <code type="erl"><![CDATA[ ... @@ -1257,12 +1202,10 @@ format_status(Opt, [_PDict,State,Data]) -> ]]></code> <p> It is not mandatory to implement a - <seealso marker="stdlib:gen_statem#Module:format_status/2"> - <c>Module:format_status/2</c> - </seealso> - function. If you do not a default implementation is used that + <seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso> + function. If you do not, a default implementation is used that does the same as this example function without filtering - the <c>Data</c> term that is: <c>StateData = {State,Data}</c>. + the <c>Data</c> term, that is, <c>StateData = {State,Data}</c>. </p> </section> @@ -1272,57 +1215,54 @@ format_status(Opt, [_PDict,State,Data]) -> <title>Complex State</title> <p> The callback mode - <seealso marker="stdlib:gen_statem#type-callback_mode"> - <c>handle_event_function</c> - </seealso> - enables using a non-atom state as described in - <seealso marker="#callback_modes"> - Callback Modes, - </seealso> - for example a complex state term like a tuple. + <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso> + enables using a non-atom state as described in section + <seealso marker="#callback_modes">Callback Modes</seealso>, + for example, a complex state term like a tuple. </p> <p> 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 <c>open</c> state immediately locks the door, + (this is the state item in question), + which in the <c>open</c> state immediately locks the door, and an API function <c>set_lock_button/1</c> to set the lock button. </p> <p> Suppose now that we call <c>set_lock_button</c> 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 <c>locked</c>. </p> <p> - So let us make the <c>button/1</c> function synchronous - by using <c>gen_statem:call</c>, + So we make the <c>button/1</c> function synchronous + by using + <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso> and still postpone its events in the <c>open</c> state. Then a call to <c>button/1</c> during the <c>open</c> - state will not return until the state transits to <c>locked</c> - since it is there the event is handled and the reply is sent. + state does not return until the state transits to <c>locked</c>, + as it is there the event is handled and the reply is sent. </p> <p> - If now one process calls <c>set_lock_button/1</c> - to change the lock button while some other process - hangs in <c>button/1</c> with the new lock button - it could be expected that the hanging lock button call + If a process now calls <c>set_lock_button/1</c> + to change the lock button while another process + hangs in <c>button/1</c> 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. </p> <p> - We define the state as <c>{StateName,LockButton}</c> + We define the state as <c>{StateName,LockButton}</c>, where <c>StateName</c> is as before and <c>LockButton</c> is the current lock button: </p> @@ -1441,10 +1381,9 @@ format_status(Opt, [_PDict,State,Data]) -> end. ]]></code> <p> - It may be an ill-fitting model for a physical code lock - that the <c>button/1</c> 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 <c>button/1</c> call can hang until the lock + is locked. But for an API in general it is not that strange. </p> </section> @@ -1457,26 +1396,21 @@ 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 - <seealso marker="stdlib:proc_lib#hibernate/3"> - <c>proc_lib:hibernate/3</c>. - </seealso> + is a problem, then the memory footprint of a server + can be mimimized by hibernating it through + <seealso marker="stdlib:proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>. </p> <note> <p> - To hibernate a process is rather costly. See - <seealso marker="erts:erlang#hibernate/3"> - <c>erlang:hibernate/3</c>. - </seealso> - It is in general not something you want to do - after every event. + It is rather costly to hibernate a process; see + <seealso marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seealso>. + It is not something you want to do after every event. </p> </note> <p> - We can in this example hibernate in the <c>{open,_}</c> state - since what normally happens in that state is that - the state timeout after a while + We can in this example hibernate in the <c>{open,_}</c> state, + because what normally occurs in that state is that + the state time-out after a while triggers a transition to <c>{locked,_}</c>: </p> <code type="erl"><![CDATA[ @@ -1493,29 +1427,27 @@ handle_event( ]]></code> <p> The - <seealso marker="stdlib:gen_statem#type-hibernate"> - <c>[hibernate]</c> - </seealso> + <seealso marker="stdlib:gen_statem#type-hibernate"><c>[hibernate]</c></seealso> action list on the last line when entering the <c>{open,_}</c> state is the only change. - If any event arrives in the <c>{open,_},</c> state we - do not bother to re-hibernate, so the server stays + If any event arrives in the <c>{open,_},</c> state, we + do not bother to rehibernate, so the server stays awake after any event. </p> <p> To change that we would need to insert - the <c>hibernate</c> action in more places, - for example for the state independent <c>set_lock_button</c> + action <c>hibernate</c> in more places. + For example, for the state-independent <c>set_lock_button</c> and <c>code_length</c> operations that then would have to be aware of using <c>hibernate</c> while in the - <c>{open,_}</c> state which would clutter the code. + <c>{open,_}</c> state, which would clutter the code. </p> <p> - 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. </p> </section> |