From 09d138e229846b7056331151135b7c8a52dc476f Mon Sep 17 00:00:00 2001
From: xsipewe
This section is to be read with the
- 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.
+
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:
These relations are interpreted as meaning:
- If we are in state These relations are interpreted as follows:
+ if we are in state
- Note that
- Since
- Like most
- The
+ In mode
- In the mode
-
+ In mode
- Both these modes allow other return tuples
- that you can find in the
+ Both these modes allow other return tuples; see
The two
- 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
- 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
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
- 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
- This is an example starting off as equivalent to the the example in the
-
- 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.
- We can implement such a code lock state machine using
+ This code lock state machine can be implemented using
- In the example in the previous section, the
-
- The first argument,
State(S) x Event(E) -> Actions(A), State(S')
-
+
+
StateName(EventType, EventContent, Data) ->
.. code for actions here ...
{next_state, NewStateName, NewData}.
-
+
handle_event(EventType, EventContent, State, Data) ->
.. code for actions here ...
{next_state, State', Data'}
+
gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
]]>
- If the name is omitted, the
- The second argument,
- The interface functions (
- The third argument,
- The fourth argument,
If name registration succeeds, the new
+
Function
+ Function
The function notifying the code lock about a button event is
implemented using
The first argument is the name of the
The event is made into a message and sent to the
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
- 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
- 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,
@@ -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