From d840b24857a1d54419953661f70716c449c11864 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Thu, 3 Mar 2016 10:54:01 +0100
Subject: Fix most of the system docs and emacs mode
---
system/doc/definitions/term.defs | 1 +
system/doc/design_principles/appup_cookbook.xml | 5 +++--
system/doc/design_principles/sup_princ.xml | 6 ++++--
system/doc/reference_manual/modules.xml | 3 ++-
4 files changed, 10 insertions(+), 5 deletions(-)
(limited to 'system')
diff --git a/system/doc/definitions/term.defs b/system/doc/definitions/term.defs
index 6091a46a20..921175a7f0 100644
--- a/system/doc/definitions/term.defs
+++ b/system/doc/definitions/term.defs
@@ -76,6 +76,7 @@ the module Erlang in the application kernel","kenneth"},
{"gen_event","gen_event","A behaviour used for programming event handling mechanisms, such as alarm handlers, error loggers, and plug-and-play handlers.","mbj"},
{"gen_fsm","gen_fsm","A behaviour used for programming finite state machines.","mbj"},
{"gen_server","gen_server","A behaviour used for programming client-server processes.","mbj"},
+{"gen_statem","gen_statem","A behaviour used for programming generic state machines.","raimo"},
{"gterm","Global Glossary Database","A glossary database used to list common acronymns and defintions etc.","jocke"},
{"xref","xref","A cross reference tool that can be used for finding dependencies between functions, modules, applications and releases. Part of the Tools application.","gunilla"},
{"GSlong","Graphics System","A library module which provides a graphics interface for Erlang.","mbj"},
diff --git a/system/doc/design_principles/appup_cookbook.xml b/system/doc/design_principles/appup_cookbook.xml
index 31335197d7..4f23c42c59 100644
--- a/system/doc/design_principles/appup_cookbook.xml
+++ b/system/doc/design_principles/appup_cookbook.xml
@@ -4,7 +4,7 @@
- 20032014
+ 20032016
Ericsson AB. All Rights Reserved.
@@ -50,7 +50,8 @@
In a system implemented according to the OTP design principles,
all processes, except system processes and special processes,
reside in one of the behaviours supervisor,
- gen_server, gen_fsm, or gen_event. These
+ gen_server, gen_fsm,
+ gen_statem or gen_event. These
belong to the STDLIB application and upgrading/downgrading
normally requires an emulator restart.
OTP thus provides no support for changing residence modules except
diff --git a/system/doc/design_principles/sup_princ.xml b/system/doc/design_principles/sup_princ.xml
index 5e2f6ba9cb..a6b7dbb68d 100644
--- a/system/doc/design_principles/sup_princ.xml
+++ b/system/doc/design_principles/sup_princ.xml
@@ -4,7 +4,7 @@
- 19972014
+ 19972016
Ericsson AB. All Rights Reserved.
@@ -213,6 +213,7 @@ child_spec() = #{id => child_id(), % mandatory
- supervisor:start_link
- gen_server:start_link
- gen_fsm:start_link
+ - gen_statem:start_link
- gen_event:start_link
- A function compliant with these functions. For details,
see the supervisor(3) manual page.
@@ -276,7 +277,8 @@ child_spec() = #{id => child_id(), % mandatory
modules are to be a list with one element
[Module], where Module is the name of
the callback module, if the child process is a supervisor,
- gen_server or gen_fsm. If the child process is a gen_event,
+ gen_server, gen_fsm or gen_statem.
+ If the child process is a gen_event,
the value shall be dynamic.
This information is used by the release handler during
upgrades and downgrades, see
diff --git a/system/doc/reference_manual/modules.xml b/system/doc/reference_manual/modules.xml
index 5f2ac2a67d..96968b547e 100644
--- a/system/doc/reference_manual/modules.xml
+++ b/system/doc/reference_manual/modules.xml
@@ -4,7 +4,7 @@
- 20032015
+ 20032016
Ericsson AB. All Rights Reserved.
@@ -144,6 +144,7 @@ fact(0) -> % |
- gen_server
- gen_fsm
+ - gen_statem
- gen_event
- supervisor
--
cgit v1.2.3
From 7563c1929cd9bfceced790d0784210ea287d536e Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Wed, 6 Apr 2016 17:13:08 +0200
Subject: Write Design Principles chapter
---
system/doc/design_principles/Makefile | 4 +-
system/doc/design_principles/code_lock.dia | Bin 0 -> 2968 bytes
system/doc/design_principles/code_lock.gif | Bin 0 -> 35031 bytes
system/doc/design_principles/code_lock_2.dia | Bin 0 -> 2646 bytes
system/doc/design_principles/code_lock_2.gif | Bin 0 -> 29420 bytes
system/doc/design_principles/part.xml | 3 +-
system/doc/design_principles/statem.xml | 1082 ++++++++++++++++++++++++++
system/doc/design_principles/xmlfiles.mk | 3 +-
8 files changed, 1089 insertions(+), 3 deletions(-)
create mode 100644 system/doc/design_principles/code_lock.dia
create mode 100644 system/doc/design_principles/code_lock.gif
create mode 100644 system/doc/design_principles/code_lock_2.dia
create mode 100644 system/doc/design_principles/code_lock_2.gif
create mode 100644 system/doc/design_principles/statem.xml
(limited to 'system')
diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile
index 29df484279..2ccf477e83 100644
--- a/system/doc/design_principles/Makefile
+++ b/system/doc/design_principles/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1997-2012. All Rights Reserved.
+# Copyright Ericsson AB 1997-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -48,6 +48,8 @@ BOOK_FILES = book.xml
GIF_FILES = \
note.gif \
clientserver.gif \
+ code_lock.gif \
+ code_lock_2.gif \
dist1.gif \
dist2.gif \
dist3.gif \
diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia
new file mode 100644
index 0000000000..d8262138a3
Binary files /dev/null and b/system/doc/design_principles/code_lock.dia differ
diff --git a/system/doc/design_principles/code_lock.gif b/system/doc/design_principles/code_lock.gif
new file mode 100644
index 0000000000..adcfee7eff
Binary files /dev/null and b/system/doc/design_principles/code_lock.gif differ
diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia
new file mode 100644
index 0000000000..4e82a9e1d6
Binary files /dev/null and b/system/doc/design_principles/code_lock_2.dia differ
diff --git a/system/doc/design_principles/code_lock_2.gif b/system/doc/design_principles/code_lock_2.gif
new file mode 100644
index 0000000000..150515c3b6
Binary files /dev/null and b/system/doc/design_principles/code_lock_2.gif differ
diff --git a/system/doc/design_principles/part.xml b/system/doc/design_principles/part.xml
index 42fd3beb6a..6495211e04 100644
--- a/system/doc/design_principles/part.xml
+++ b/system/doc/design_principles/part.xml
@@ -4,7 +4,7 @@
- 19972013
+ 19972016
Ericsson AB. All Rights Reserved.
@@ -31,6 +31,7 @@
+
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
new file mode 100644
index 0000000000..c10afe482d
--- /dev/null
+++ b/system/doc/design_principles/statem.xml
@@ -0,0 +1,1082 @@
+
+
+
+
+
+
+ 2016
+ Ericsson AB. All Rights Reserved.
+
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+ gen_statem Behaviour
+
+
+
+
+ statem.xml
+
+
+
+ This section is to be read with the
+ gen_statem(3)
+ manual page in STDLIB, where all interface functions and callback
+ functions are described in detail.
+
+
+
+
+
+ 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
+ of the input (and the state) and that they are
+ some kind of values.
+
+
+ For an Event Driven State Machine the input is an event
+ that triggers a state transition and the output
+ is actions executed during the state transition.
+ It can analogously to the mathematical model of a
+ Finite State Machine be described as
+ a set of relations of the 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
+ are to perform actions A and make a transition to
+ state S'.
+
+
+ Note that S' may 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.
+
+
+ Like most gen_ behaviours, gen_statem keeps
+ a server Data besides the state. This and the fact that
+ 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.
+
+
+ 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:
+
+
+StateName(EventType, EventContent, Data) ->
+ .. code for actions here ...
+ {next_state, StateName', Data'}.
+
+ In the mode handle_event_function there is only one
+ Erlang function that implements 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
+
+ reference manual.
+
+ These other return tuples can for example stop the machine,
+ execute state transition actions on the machine engine itself
+ and send replies.
+
+
+
+
+
+
+ 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
+ using features in gen_statem that gen_fsm does not have.
+ At the end of this section you can find 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.
+ 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.
+
+
+ Code lock state diagram
+
+
+ We can implement such a code lock state machine using
+ gen_statem with the following callback module:
+
+
+
+ gen_statem:start_link({local,code_lock}, ?MODULE, Code, []).
+
+button(Digit) ->
+ gen_statem:cast(code_lock, {button,Digit}).
+
+
+init(Code) ->
+ do_lock(),
+ Data = #{code => Code, remaining => Code},
+ {state_functions,locked,Data}.
+
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] ->
+ do_unlock(),
+ {next_state,open,Data#{remaining := Code},10000};
+ [Digit|Rest] -> % Incomplete
+ {next_state,locked,Data#{remaining := Rest}};
+ _Wrong ->
+ {next_state,locked,Data#{remaining := Code}}
+ end.
+
+open(timeout, _, Data) ->
+ do_lock(),
+ {next_state,locked,Data};
+open(cast, {button,_}, Data) ->
+ do_lock(),
+ {next_state,locked,Data}.
+
+do_lock() ->
+ io:format("Lock~n", []).
+do_unlock() ->
+ io:format("Unlock~n", []).
+
+terminate(_Reason, State, _Data) ->
+ State =/= locked andalso do_lock(),
+ ok.
+code_change(_Vsn, State, Data, _Extra) ->
+ {ok,State,Data}.
+ ]]>
+ The code is explained in the next sections.
+
+
+
+
+
+ Starting gen_statem
+
+ In the example in the previous section, the gen_statem is
+ started by calling code_lock:start_link(Code):
+
+
+ gen_statem:start_link({local,code_lock}, ?MODULE, Code, []).
+ ]]>
+
+ start_link calls the function
+
+ gen_statem:start_link/4
+
+ which spawns and links to a new process; a gen_statem.
+
+
+ -
+
+ The first argument, {local,code_lock}, specifies
+ the name. In this case, the gen_statem is locally
+ registered as code_lock.
+
+
+ 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.
+
+
+
+ -
+
+ 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 third argument, Code, is a list of digits that
+ is the correct unlock code which is passsed
+ to the callback function init/1.
+
+
+ -
+
+ The fourth argument, [], is a list of options. See the
+
+ gen_statem:start_link/3
+
+ manual page for available options.
+
+
+
+
+ If name registration succeeds, the new gen_statem process
+ calls the callback function code_lock:init(Code).
+ This function is expected to return {CallbackMode,State,Data},
+ where CallbackMode selects callback module state function
+ mode, in this case state_functions 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.
+ Here the server data is a
+
+ map
+
+ with the key code that stores
+ the correct button sequence and the key remaining
+ that stores the remaining correct button sequence
+ (the same as the code to begin with).
+
+
+ do_lock(),
+ Data = #{code => Code, remaining => Code},
+ {state_functions,locked,Data}.
+ ]]>
+
+
+ gen_statem:start_link
+
+ is synchronous. It does not return until the gen_statem
+ has been initialized and is ready to receive events.
+
+
+
+ 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;
+
+ gen_statem:start
+
+ to start a standalone gen_statem, that is;
+ a gen_statem that is not part of a supervision tree.
+
+
+
+
+
+
+ Events and Handling them
+ The function notifying the code lock about a button event is
+ implemented using
+
+ gen_statem:cast/2:
+
+
+
+ gen_statem:cast(code_lock, {button,Digit}).
+ ]]>
+
+ code_lock is the name of the gen_statem and must
+ agree with the name used to start it.
+ {button,Digit} is the actual event content.
+
+
+ The event is made into a message and sent to the gen_statem.
+ When the event is received, the gen_statem calls
+ StateName(cast, Event, Data), which is expected to
+ return a tuple {next_state,NewStateName,NewData}.
+ StateName is the name of the current state and
+ NewStateName is the name of the next state to go to.
+ NewData is a new value for the server data of
+ the gen_statem.
+
+
+ case Remaining of
+ [Digit] -> % Complete
+ do_unlock(),
+ {next_state,open,Data#{remaining := Code},10000};
+ [Digit|Rest] -> % Incomplete
+ {next_state,locked,Data#{remaining := Rest}};
+ [_|_] -> % Wrong
+ {next_state,locked,Data#{remaining := Code}}
+ end.
+
+open(timeout, _, Data) ->
+ do_lock(),
+ {next_state,locked,Data};
+open(cast, {button,_}, Data) ->
+ do_lock(),
+ {next_state,locked,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
+ and the gen_statem goes to state open,
+ or the door remains in state locked.
+
+
+ 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.
+
+
+
+
+ Event Time-Outs
+
+ When a correct code has been given, the door is unlocked and
+ the following tuple is returned from locked/2:
+
+
+
+ 10000 is a time-out value in milliseconds.
+ After this time, that is; 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:
+
+
+ do_lock(),
+ {next_state,locked,Data};
+ ]]>
+
+
+
+
+
+ All State Events
+
+ Sometimes an event 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
+ 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.
+
+
+ gen_statem:call(code_lock, code_length).
+
+...
+locked(...) -> ... ;
+locked(EventType, EventContent, Data) ->
+ handle_event(EventType, EventContent, Data).
+
+...
+open(...) -> ... ;
+open(EventType, EventContent, Data) ->
+ handle_event(EventType, EventContent, Data).
+
+handle_event({call,From}, code_length, #{code := Code} = Data) ->
+ {keep_state,Data,[{reply,From,length(Code)}]}.
+ ]]>
+
+ 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
+ that retains the current state.
+
+
+
+
+
+
+ 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
+ where we dispatch on event first and then state:
+
+
+ Data = #{code => Code, remaining => Code},
+ {handle_event_function,locked,Data}.
+
+handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
+ case State of
+ locked ->
+ case maps:get(remaining, Data) of
+ [Digit] -> % Complete
+ do_unlock(),
+ {next_state,open,Data#{remaining := Code},10000};
+ [Digit|Rest] -> % Incomplete
+ {keep_state,Data#{remaining := Rest}};
+ [_|_] -> % Wrong
+ {keep_state,Data#{remaining := Code}}
+ end;
+ open ->
+ do_lock(),
+ {next_state,locked,Data}
+ end;
+handle_event(timeout, _, open, Data) ->
+ do_lock(),
+ {next_state,locked,Data}.
+
+...
+ ]]>
+
+
+
+
+
+ Stopping
+
+
+ In a Supervision Tree
+
+ If the gen_statem is part of a supervision tree,
+ no stop function is needed.
+ The gen_statem is automatically terminated by its supervisor.
+ Exactly how this is done is defined by a
+ shutdown strategy
+ 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
+ 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),
+ ...
+ {CallbackMode,State,Data}.
+ ]]>
+
+ 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.
+
+
+ State =/= locked andalso do_lock(),
+ ok.
+ ]]>
+
+
+
+ Standalone gen_statem
+
+ If the gen_statem is not part of a supervision tree,
+ it can be stopped using
+
+ gen_statem:stop,
+
+ preferably through an API function:
+
+
+ gen_statem:stop(code_lock).
+ ]]>
+
+ This makes the gen_statem call the terminate/3
+ callback function just like for a supervised server
+ and waits for the process to terminate.
+
+
+
+
+
+
+
+ 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
+ callback function before returning
+ to the gen_statem engine.
+
+
+ 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
+
+ actions
+
+ in the
+
+ return tuple
+
+ from the
+
+ callback function.
+
+ These state transition actions affect the gen_statem
+ engine itself. They can:
+
+
+ - Postpone the current event.
+ - Hibernate the gen_statem.
+ - Start an event timeout.
+ - 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
+
+ documentation
+
+ for details. You can for example actually reply to several callers
+ and generate multiple next events to handle.
+
+
+
+
+
+
+ Event Types
+
+ So far we have mentioned a few
+
+ 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
+ they come from:
+
+
+ cast
+ -
+ Generated by
+
+ gen_statem:cast.
+
+
+ {call,From}
+ -
+ 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.
+
+
+ info
+ -
+ Generated by any regular process message sent to
+ the gen_statem process.
+
+ timeout
+ -
+ Generated by the state transition action
+ {timeout,Time,EventContent} (or its short form Time)
+ timer timing out.
+
+ internal
+ -
+ Generated by the state transition action
+ {next_event,internal,EventContent}.
+ In fact all event types above can be generated using
+ {next_event,EventType,EventContent}.
+
+
+
+
+
+
+
+ State Timeouts
+
+ 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.
+
+
+ Often you want a timer to not 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:
+
+ erlang:start_timer.
+
+
+
+ Looking at the example in this chapter so far; 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.
+
+
+ Suppose 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:
+
+
+ case Remaining of
+ [Digit] ->
+ do_unlock(),
+ Tref = erlang:start_timer(10000, self(), lock),
+ {next_state,open,Data#{remaining := Code, timer := Tref}};
+...
+
+open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
+ do_lock(),
+ {next_state,locked,Data};
+open(cast, {button,_}, Data) ->
+ {keep_state,Data};
+...
+ ]]>
+
+ If you need to cancel a timer due to some other event you can use
+
+ erlang:cancel_timer(Tref).
+
+ Note that a timeout message can not arrive after this,
+ unless you have postponed it before (why on earth one would do that).
+
+
+ Another way to cancel a timer is to not cancel it,
+ but instead to ignore it if it arrives in a state
+ where it is known to be late.
+
+
+
+
+
+
+ Postponing Events
+
+ 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.
+
+
+ Postponing is ordered by the
+
+ state transition action
+
+ postpone.
+
+
+ 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:
+
+
+ {keep_state,Data,[postpone]};
+...
+ ]]>
+
+ Fuzzy State Diagrams
+
+ It is not uncommon that a state diagram does not specify
+ how to handle events that are not illustrated
+ in a particular state in the diagram.
+ Hopefully this is described in an associated text
+ or from the context.
+
+
+ Possible actions may be; ignore as in drop the event
+ (maybe log it) or deal with the event in some other state
+ as in postpone it.
+
+
+
+ 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:
+
+
+ spawn(
+ fun () ->
+ true = register(code_lock, self()),
+ do_lock(),
+ locked(Code, Code)
+ end).
+
+button(Digit) ->
+ code_lock ! {button,Digit}.
+
+locked(Code, [Digit|Remaining]) ->
+ receive
+ {button,Digit} when Remaining =:= [] ->
+ do_unlock(),
+ open(Code);
+ {button,Digit} ->
+ locked(Code, Remaining);
+ {button,_} ->
+ locked(Code, Code)
+ end.
+
+open(Code) ->
+ receive
+ after 10000 ->
+ do_lock(),
+ locked(Code, Code)
+ end.
+
+do_lock() ->
+ io:format("Locked~n", []).
+do_unlock() ->
+ io:format("Open~n", []).
+ ]]>
+
+ The selective receive in this case causes open
+ to implicitly postpone any events to the locked state.
+
+
+ The
+
+ state transition action
+
+ postpone is designed to be able to model
+ selective receive. Selective receive implicitly postpones
+ any not received events, but the postpone
+ state transition action explicitly postpones a received event.
+
+
+ Other than that both mechanisms have got the same theoretical
+ time and memory complexity, while the selective receive
+ language construct has got smaller constant factors.
+
+
+
+
+
+
+
+ Self Generated Events
+
+ It may be beneficial in some cases to be able to generate events
+ to your own state machine. This can be done with the
+
+ state transition action
+
+ {next_event,EventType,EventContent}.
+
+
+ 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,
+ 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
+ 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
+ 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
+ insert the internal event
+ or use state transition function.
+
+
+ Here is an implementation of entry actions
+ using internal events with content enter
+ utilizing a helper function enter/3 for state entry:
+
+
+ Data = #{code => Code},
+ enter(state_functions, locked, Data).
+
+...
+locked(internal, enter, _Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] ->
+ enter(next_state, open, Data);
+...
+
+open(internal, enter, _Data) ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer => Tref}};
+open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
+ enter(next_state, locked, Data);
+...
+
+enter(Tag, State, Data) ->
+ {Tag,State,Data,[{next_event,internal,enter}]}.
+ ]]>
+
+
+
+
+
+ Example Revisited
+
+ Here is the example after all mentioned modifications
+ and some more utilizing the entry actions,
+ which deserves a new state diagram:
+
+
+ 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.
+
+
+ Callback Mode: state_functions
+
+ Using state functions:
+
+
+ gen_statem:start_link({local,code_lock}, ?MODULE, Code, []).
+stop() ->
+ gen_statem:stop(code_lock).
+
+button(Digit) ->
+ gen_statem:cast(code_lock, {button,Digit}).
+code_length() ->
+ gen_statem:call(code_lock, code_length).
+
+init(Code) ->
+ Data = #{code => Code},
+ enter(state_functions, locked, Data).
+
+locked(internal, enter, #{code := Code} = Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] -> % Complete
+ enter(next_state, open, Data);
+ [Digit|Rest] -> % Incomplete
+ {keep_state,Data#{remaining := Rest}};
+ [_|_] -> % Wrong
+ {keep_state,Data#{remaining := Code}}
+ end;
+locked(EventType, EventContent, Data) ->
+ handle_event(EventType, EventContent, Data).
+
+open(internal, enter, Data) ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer => Tref}};
+open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
+ enter(next_state, locked, Data);
+open(cast, {button,_}, _) ->
+ {keep_state_and_data,[postpone]};
+open(EventType, EventContent, Data) ->
+ handle_event(EventType, EventContent, Data).
+
+handle_event({call,From}, code_length, #{code := Code}) ->
+ {keep_state_and_data,[{reply,From,length(Code)}]}.
+enter(Tag, State, Data) ->
+ {Tag,State,Data,[{next_event,internal,enter}]}.
+
+do_lock() ->
+ io:format("Locked~n", []).
+do_unlock() ->
+ io:format("Open~n", []).
+
+terminate(_Reason, State, _Data) ->
+ State =/= locked andalso do_lock(),
+ ok.
+code_change(_Vsn, State, Data, _Extra) ->
+ {ok,State,Data}.
+ ]]>
+
+
+ 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:
+
+
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ enter(handle_event_function, locked, Data).
+
+...
+
+%% State: locked
+handle_event(internal, enter, locked, #{code := Code} = Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+handle_event(
+ cast, {button,Digit}, locked,
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] -> % Complete
+ enter(next_state, open, Data, []);
+ [Digit|Rest] -> % Incomplete
+ {keep_state,Data#{remaining := Rest}};
+ [_|_] -> % Wrong
+ {keep_state,Data#{remaining := Code}}
+ end;
+%%
+%% State: open
+handle_event(internal, enter, open, Data) ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer => Tref}};
+handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
+ enter(next_state, locked, Data, []);
+handle_event(cast, {button,_}, open, _) ->
+ {keep_state_and_data,[postpone]};
+%%
+%% Any state
+handle_event({call,From}, code_length, _State, #{code := Code}) ->
+ {keep_state_and_data,[{reply,From,length(Code)}]}.
+
+...
+ ]]>
+
+
+ Note that postponing buttons from the locked state
+ to the open state feels like the wrong thing to do
+ for a code lock, but it illustrates event postponing.
+
+
+
+
diff --git a/system/doc/design_principles/xmlfiles.mk b/system/doc/design_principles/xmlfiles.mk
index 3032ac3ab9..e476255d62 100644
--- a/system/doc/design_principles/xmlfiles.mk
+++ b/system/doc/design_principles/xmlfiles.mk
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2009. All Rights Reserved.
+# Copyright Ericsson AB 2009-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ DESIGN_PRINCIPLES_CHAPTER_FILES = \
distributed_applications.xml \
events.xml \
fsm.xml \
+ statem.xml \
gen_server_concepts.xml \
included_applications.xml \
release_handling.xml \
--
cgit v1.2.3
From 20b44db2843e280474a45665f2584f9130eb686b Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Fri, 15 Apr 2016 10:40:00 +0200
Subject: Use .png pictures instead of .gif
---
system/doc/design_principles/Makefile | 17 ++++++++++++-----
system/doc/design_principles/code_lock.gif | Bin 35031 -> 0 bytes
system/doc/design_principles/code_lock.png | Bin 0 -> 58906 bytes
system/doc/design_principles/code_lock_2.gif | Bin 29420 -> 0 bytes
system/doc/design_principles/code_lock_2.png | Bin 0 -> 51848 bytes
system/doc/design_principles/statem.xml | 4 ++--
6 files changed, 14 insertions(+), 7 deletions(-)
delete mode 100644 system/doc/design_principles/code_lock.gif
create mode 100644 system/doc/design_principles/code_lock.png
delete mode 100644 system/doc/design_principles/code_lock_2.gif
create mode 100644 system/doc/design_principles/code_lock_2.png
(limited to 'system')
diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile
index 2ccf477e83..937b3e28c8 100644
--- a/system/doc/design_principles/Makefile
+++ b/system/doc/design_principles/Makefile
@@ -48,8 +48,6 @@ BOOK_FILES = book.xml
GIF_FILES = \
note.gif \
clientserver.gif \
- code_lock.gif \
- code_lock_2.gif \
dist1.gif \
dist2.gif \
dist3.gif \
@@ -60,6 +58,12 @@ GIF_FILES = \
sup5.gif \
sup6.gif
+PNG_FILES = \
+ code_lock.png \
+ code_lock_2.png
+
+IMAGE_FILES = $(GIF_FILES) $(PNG_FILES)
+
XML_FILES = \
$(BOOK_FILES) $(XML_CHAPTER_FILES) \
$(XML_PART_FILES)
@@ -87,13 +91,16 @@ _create_dirs := $(shell mkdir -p $(HTMLDIR))
$(HTMLDIR)/%.gif: %.gif
$(INSTALL_DATA) $< $@
+$(HTMLDIR)/%.png: %.png
+ $(INSTALL_DATA) $< $@
+
docs: html
local_docs: PDFDIR=../../pdf
-html: $(HTML_UG_FILE) gifs
+html: $(HTML_UG_FILE) images
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
+images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
debug opt:
@@ -111,7 +118,7 @@ release_docs_spec: docs
# $(INSTALL_DIR) "$(RELEASE_PATH)/pdf"
# $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELEASE_PATH)/pdf"
$(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(GIF_FILES) $(HTMLDIR)/*.html \
+ $(INSTALL_DATA) $(IMAGE_FILES) $(HTMLDIR)/*.html \
$(RELSYSDIR)
diff --git a/system/doc/design_principles/code_lock.gif b/system/doc/design_principles/code_lock.gif
deleted file mode 100644
index adcfee7eff..0000000000
Binary files a/system/doc/design_principles/code_lock.gif and /dev/null differ
diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png
new file mode 100644
index 0000000000..5e5744ecc2
Binary files /dev/null and b/system/doc/design_principles/code_lock.png differ
diff --git a/system/doc/design_principles/code_lock_2.gif b/system/doc/design_principles/code_lock_2.gif
deleted file mode 100644
index 150515c3b6..0000000000
Binary files a/system/doc/design_principles/code_lock_2.gif and /dev/null differ
diff --git a/system/doc/design_principles/code_lock_2.png b/system/doc/design_principles/code_lock_2.png
new file mode 100644
index 0000000000..138fbdef6c
Binary files /dev/null and b/system/doc/design_principles/code_lock_2.png differ
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index c10afe482d..65daeac782 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -136,7 +136,7 @@ handle_event(EventType, EventContent, State, Data) ->
it is is wrong, we start all over,
waiting for a new button sequence.
-
+
Code lock state diagram
@@ -934,7 +934,7 @@ enter(Tag, State, Data) ->
and some more utilizing the entry actions,
which deserves a new state diagram:
-
+
Code lock state diagram revisited
--
cgit v1.2.3
From a2f3c685b5aeac798e12302cf8fe7df13184b669 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Fri, 15 Apr 2016 10:09:54 +0200
Subject: Introduce corrections from Fred Hebert and Ingela
---
system/doc/design_principles/statem.xml | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
(limited to 'system')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 65daeac782..02754bd23d 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -90,7 +90,7 @@ State(S) x Event(E) -> Actions(A), State(S')
StateName(EventType, EventContent, Data) ->
.. code for actions here ...
- {next_state, StateName', Data'}.
+ {next_state, NewStateName, NewData}.
In the mode handle_event_function there is only one
Erlang function that implements all state transition rules:
@@ -612,7 +612,7 @@ stop() ->
An example of event postponing comes in later in this chapter.
See the
- documentation
+ reference manual
for details. You can for example actually reply to several callers
and generate multiple next events to handle.
@@ -735,7 +735,8 @@ open(cast, {button,_}, Data) ->
erlang:cancel_timer(Tref).
Note that a timeout message can not arrive after this,
- unless you have postponed it before (why on earth one would do that).
+ unless you have postponed it (see the next section) before,
+ so make sure you do not accidentally postpone such messages.
Another way to cancel a timer is to not cancel it,
@@ -837,15 +838,25 @@ do_unlock() ->
The selective receive in this case causes open
to implicitly 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
+ sys
+ compatible behaviours must respond to system messages and therefore
+ do that in their engine receive loop,
+ passing non-system messages to the callback module.
+
The
state transition action
postpone is designed to be able to model
- selective receive. Selective receive implicitly postpones
+ selective receives. A selective receive implicitly postpones
any not received events, but the postpone
- state transition action explicitly postpones a received event.
+ state transition action explicitly postpones one received event.
Other than that both mechanisms have got the same theoretical
--
cgit v1.2.3
From 2591124991bf601321d44de37d6c32e49075e68f Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Mon, 18 Apr 2016 15:39:06 +0200
Subject: Introduce Fred Herbert suggested additions
---
system/doc/design_principles/statem.xml | 247 +++++++++++++++++++++++++++++++-
1 file changed, 243 insertions(+), 4 deletions(-)
(limited to 'system')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 02754bd23d..26e4840640 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -71,6 +71,7 @@ State(S) x Event(E) -> Actions(A), State(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)
Like most gen_ behaviours, gen_statem keeps
@@ -81,9 +82,19 @@ State(S) x Event(E) -> Actions(A), State(S')
a state machine implemented with this behaviour Turing complete.
But it feels mostly like an Event Driven Mealy Machine.
+
+
+
+
+
+
+ Callback Modes
The gen_statem behaviour supports two different
- callback modes. In the mode state_functions,
+
+ 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:
@@ -109,6 +120,64 @@ handle_event(EventType, EventContent, State, Data) ->
execute state transition actions on the machine engine itself
and send replies.
+
+
+ Choosing Callback Mode
+
+ The two
+
+ callback modes
+
+ gives 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.
+
+
+ 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
+ hence 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
+ 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.
+
+
+ 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
+ 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.
+ 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
+ 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.
+
+
@@ -773,6 +842,20 @@ open(cast, {button,_}, Data) ->
{keep_state,Data,[postpone]};
...
]]>
+
+ 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.
+
+
+ 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.
+
+
Fuzzy State Diagrams
@@ -788,6 +871,7 @@ open(cast, {button,_}, Data) ->
as in postpone it.
+
Selective Receive
@@ -956,6 +1040,7 @@ enter(Tag, State, Data) ->
Nor does it show that the code_length/0 call shall be
handled in every state.
+
Callback Mode: state_functions
@@ -1029,6 +1114,7 @@ code_change(_Vsn, State, Data, _Extra) ->
{ok,State,Data}.
]]>
+
Callback Mode: handle_event_function
@@ -1059,7 +1145,7 @@ handle_event(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] -> % Complete
- enter(next_state, open, Data, []);
+ enter(next_state, open, Data);
[Digit|Rest] -> % Incomplete
{keep_state,Data#{remaining := Rest}};
[_|_] -> % Wrong
@@ -1072,7 +1158,7 @@ handle_event(internal, enter, open, Data) ->
do_unlock(),
{keep_state,Data#{timer => Tref}};
handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data, []);
+ enter(next_state, locked, Data);
handle_event(cast, {button,_}, open, _) ->
{keep_state_and_data,[postpone]};
%%
@@ -1086,8 +1172,161 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
Note that postponing buttons from the locked state
to the open state feels like the wrong thing to do
- for a code lock, but it illustrates event postponing.
+ for a code lock, but it at least illustrates event postponing.
+
+
+
+ Complex State
+
+ The
+
+ callback mode
+
+ handle_event_function
+ enables using a non-atom state as described in
+
+ 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
+ by introducing a configurable lock button
+ (this is the state item in question)
+ that 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 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,
+ 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.
+
+
+ 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
+ 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.
+
+
+ We define the state as {StateName,LockButton}
+ where StateName is as before
+ and LockButton is the current lock button:
+
+
+ gen_statem:start_link(
+ {local,?NAME}, ?MODULE, {Code,LockButton}, []).
+stop() ->
+ gen_statem:stop(?NAME).
+
+button(Digit) ->
+ gen_statem:call(?NAME, {button,Digit}).
+code_length() ->
+ gen_statem:call(?NAME, code_length).
+set_lock_button(LockButton) ->
+ gen_statem:call(?NAME, {set_lock_button,LockButton}).
+
+init({Code,LockButton}) ->
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ enter(handle_event_function, {locked,LockButton}, Data, []).
+
+%% State: locked
+handle_event(internal, enter, {locked,_}, #{code := Code} = Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+handle_event(
+ {call,From}, {button,Digit}, {locked,LockButton},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] -> % Complete
+ enter(next_state, {open,LockButton}, Data, [{reply,From,ok}]);
+ [Digit|Rest] -> % Incomplete
+ {keep_state,Data#{remaining := Rest},[{reply,From,ok}]};
+ [_|_] -> % Wrong
+ {keep_state,Data#{remaining := Code},[{reply,From,ok}]}
+ end;
+%%
+%% State: open
+handle_event(internal, enter, {open,_}, Data) ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer => Tref}};
+handle_event(
+ info, {timeout,Tref,lock}, {open,LockButton},
+ #{timer := Tref} = Data) ->
+ enter(next_state, {locked,LockButton}, Data, []);
+handle_event(
+ {call,From}, {button,LockButton}, {open,LockButton},
+ #{timer := Tref} = Data) ->
+ erlang:cancel_timer(Tref),
+ enter(next_state, {locked,LockButton}, Data, [{reply,From,locked}]);
+handle_event({call,_}, {button,_}, {open,_}, _) ->
+ {keep_state_and_data,[postpone]};
+%%
+%% Any state
+handle_event(
+ {call,From}, {set_lock_button,NewLockButton}, {StateName,OldLockButton},
+ Data) ->
+ {next_state,{StateName,NewLockButton},Data,
+ [{reply,From,OldLockButton}]};
+handle_event({call,From}, code_length, _State, #{code := Code}) ->
+ {keep_state_and_data,[{reply,From,length(Code)}]}.
+
+enter(Tag, State, Data, Actions) ->
+ {Tag,State,Data,[{next_event,internal,enter}|Actions]}.
+
+do_lock() ->
+ io:format("Locked~n", []).
+do_unlock() ->
+ io:format("Open~n", []).
+
+terminate(_Reason, State, _Data) ->
+ State =/= locked andalso do_lock(),
+ ok.
+code_change(_Vsn, State, Data, _Extra) ->
+ {ok,State,Data}.
+ ]]>
+
+ 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.
+
+
+
--
cgit v1.2.3
From 2977fbc6b658b0d664f7d3b36ecf8ca9e897aaa3 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Tue, 19 Apr 2016 09:28:38 +0200
Subject: Use ?NAME macro in examples
---
system/doc/design_principles/statem.xml | 36 ++++++++++++++++++---------------
1 file changed, 20 insertions(+), 16 deletions(-)
(limited to 'system')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 26e4840640..72aaafd780 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -216,6 +216,7 @@ handle_event(EventType, EventContent, State, Data) ->
-export([locked/3,open/3]).
start_link(Code) ->
- gen_statem:start_link({local,code_lock}, ?MODULE, Code, []).
+ gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
button(Digit) ->
- gen_statem:cast(code_lock, {button,Digit}).
+ gen_statem:cast(?NAME, {button,Digit}).
init(Code) ->
@@ -278,7 +279,7 @@ code_change(_Vsn, State, Data, _Extra) ->
- gen_statem:start_link({local,code_lock}, ?MODULE, Code, []).
+ gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
]]>
start_link calls the function
@@ -290,9 +291,9 @@ start_link(Code) ->
-
- The first argument, {local,code_lock}, specifies
+ The first argument, {local,?NAME}, specifies
the name. In this case, the gen_statem is locally
- registered as code_lock.
+ registered as code_lock through the macro ?NAME.
If the name is omitted, the gen_statem is not registered.
@@ -394,11 +395,12 @@ init(Code) ->
- gen_statem:cast(code_lock, {button,Digit}).
+ gen_statem:cast(?NAME, {button,Digit}).
]]>
- code_lock is the name of the gen_statem and must
- agree with the name used to start it.
+ 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
+ same macro ?NAME as when starting.
{button,Digit} is the actual event content.
@@ -495,7 +497,7 @@ open(timeout, _, Data) ->
...
code_length() ->
- gen_statem:call(code_lock, code_length).
+ gen_statem:call(?NAME, code_length).
...
locked(...) -> ... ;
@@ -627,7 +629,7 @@ terminate(_Reason, State, _Data) ->
...
stop() ->
- gen_statem:stop(code_lock).
+ gen_statem:stop(?NAME).
]]>
This makes the gen_statem call the terminate/3
@@ -882,18 +884,19 @@ open(cast, {button,_}, Data) ->
spawn(
fun () ->
- true = register(code_lock, self()),
+ true = register(?NAME, self()),
do_lock(),
locked(Code, Code)
end).
button(Digit) ->
- code_lock ! {button,Digit}.
+ ?NAME ! {button,Digit}.
locked(Code, [Digit|Remaining]) ->
receive
@@ -1049,6 +1052,7 @@ enter(Tag, State, Data) ->
-export([locked/3,open/3]).
start_link(Code) ->
- gen_statem:start_link({local,code_lock}, ?MODULE, Code, []).
+ gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
stop() ->
- gen_statem:stop(code_lock).
+ gen_statem:stop(?NAME).
button(Digit) ->
- gen_statem:cast(code_lock, {button,Digit}).
+ gen_statem:cast(?NAME, {button,Digit}).
code_length() ->
- gen_statem:call(code_lock, code_length).
+ gen_statem:call(?NAME, code_length).
init(Code) ->
Data = #{code => Code},
--
cgit v1.2.3
From 26b3c7d60d52d8a7be006b06d856bb0f7276e77a Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Thu, 21 Apr 2016 15:58:52 +0200
Subject: Modify code_change/4 to return CallbackMode
Also move check of non-atom states in callback mode
state_functions to where the state function is called.
This gives homogenous diagnostics for state functions,
code_change/4 and system_replace_state StateFun.
Irregularities pointed out by James Fish.
---
system/doc/design_principles/statem.xml | 87 +++++++++++++++++++--------------
1 file changed, 49 insertions(+), 38 deletions(-)
(limited to 'system')
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 72aaafd780..27b9b7c761 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -90,11 +90,11 @@ State(S) x Event(E) -> Actions(A), State(S')
Callback Modes
- The gen_statem behaviour supports two different
-
- callback modes.
+ The gen_statem behaviour supports two different callback modes.
+ In the mode
+
+ state_functions,
- In the mode state_functions,
the state transition rules are written as a number of Erlang
functions, which conform to the following convention:
@@ -103,7 +103,11 @@ StateName(EventType, EventContent, Data) ->
.. code for actions here ...
{next_state, NewStateName, NewData}.
- In the mode handle_event_function there is only one
+ In the mode
+
+ handle_event_function
+
+ there is only one
Erlang function that implements all state transition rules:
@@ -125,9 +129,7 @@ handle_event(EventType, EventContent, State, Data) ->
Choosing Callback Mode
The two
-
- callback modes
-
+ callback modes
gives different possibilities
and restrictions, but one goal remains:
you want to handle all possible combinations of
@@ -186,7 +188,7 @@ handle_event(EventType, EventContent, State, Data) ->
Example
This is an example starting off as equivalent to the the example in the
- gen_fsm behaviour
+ gen_fsm behaviour
description. In later chapters 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
@@ -217,6 +219,7 @@ handle_event(EventType, EventContent, State, Data) ->
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock).
+-define(CALLBACK_MODE, state_functions).
-export([start_link/1]).
-export([button/1]).
@@ -233,7 +236,7 @@ button(Digit) ->
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {state_functions,locked,Data}.
+ {?CALLBACK_MODE,locked,Data}.
locked(
cast, {button,Digit},
@@ -264,7 +267,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {ok,State,Data}.
+ {?CALLBACK_MODE,State,Data}.
]]>
The code is explained in the next sections.
@@ -340,16 +343,20 @@ start_link(Code) ->
If name registration succeeds, the new gen_statem process
calls the callback function code_lock:init(Code).
This function is expected to return {CallbackMode,State,Data},
- where CallbackMode selects callback module state function
- mode, in this case state_functions that is each state
+ where
+
+ CallbackMode
+
+ selects callback module state function mode, in this case
+
+ state_functions
+
+ through the 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.
- Here the server data is a
-
- map
-
+ Here the server data is a map
with the key code that stores
the correct button sequence and the key remaining
that stores the remaining correct button sequence
@@ -359,7 +366,7 @@ start_link(Code) ->
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {state_functions,locked,Data}.
+ {?CALLBACK_MODE,locked,Data}.
]]>
@@ -536,13 +543,12 @@ handle_event({call,From}, code_length, #{code := Code} = Data) ->
- Data = #{code => Code, remaining => Code},
- {handle_event_function,locked,Data}.
+...
handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
case State of
@@ -598,8 +604,8 @@ handle_event(timeout, _, open, Data) ->
process_flag(trap_exit, true),
+ do_lock(),
...
- {CallbackMode,State,Data}.
]]>
In this example we let the terminate/3 function
@@ -994,11 +1000,18 @@ do_unlock() ->
utilizing a helper function enter/3 for state entry:
+ process_flag(trap_exit, true),
Data = #{code => Code},
- enter(state_functions, locked, Data).
+ enter(?CALLBACK_MODE, locked, Data).
...
+
locked(internal, enter, _Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
@@ -1053,6 +1066,7 @@ enter(Tag, State, Data) ->
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock_2).
+-define(CALLBACK_MODE, state_functions).
-export([start_link/1,stop/0]).
-export([button/1,code_length/0]).
@@ -1070,8 +1084,9 @@ code_length() ->
gen_statem:call(?NAME, code_length).
init(Code) ->
+ process_flag(trap_exit, true),
Data = #{code => Code},
- enter(state_functions, locked, Data).
+ enter(?CALLBACK_MODE, locked, Data).
locked(internal, enter, #{code := Code} = Data) ->
do_lock(),
@@ -1115,7 +1130,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {ok,State,Data}.
+ {?CALLBACK_MODE,State,Data}.
]]>
@@ -1129,14 +1144,10 @@ code_change(_Vsn, State, Data, _Extra) ->
- process_flag(trap_exit, true),
- Data = #{code => Code},
- enter(handle_event_function, locked, Data).
+-export([handle_event/4]).
...
@@ -1185,11 +1196,10 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
Complex State
- The
-
- callback mode
+ The callback mode
+
+ handle_event_function
- handle_event_function
enables using a non-atom state as described in
Callback Modes,
@@ -1245,6 +1255,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock_3).
+-define(CALLBACK_MODE, handle_event_function).
-export([start_link/2,stop/0]).
-export([button/1,code_length/0,set_lock_button/1]).
@@ -1267,7 +1278,7 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(handle_event_function, {locked,LockButton}, Data, []).
+ enter(?CALLBACK_MODE, {locked,LockButton}, Data, []).
%% State: locked
handle_event(internal, enter, {locked,_}, #{code := Code} = Data) ->
@@ -1323,7 +1334,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {ok,State,Data}.
+ {?CALLBACK_MODE,State,Data}.
]]>
It may be an ill-fitting model for a physical code lock
--
cgit v1.2.3
From b54d82fea10c24359d2a315668b6176fc47963b7 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Fri, 22 Apr 2016 15:18:07 +0200
Subject: Promote gen_statem over gen_fsm
---
system/doc/design_principles/des_princ.xml | 6 ++++--
system/doc/design_principles/fsm.xml | 12 +++++++++++-
system/doc/design_principles/release_handling.xml | 4 ++--
system/doc/design_principles/statem.xml | 9 +++++++++
system/doc/design_principles/sup_princ.xml | 2 +-
5 files changed, 27 insertions(+), 6 deletions(-)
(limited to 'system')
diff --git a/system/doc/design_principles/des_princ.xml b/system/doc/design_principles/des_princ.xml
index 0e087cf843..8ab8661c2d 100644
--- a/system/doc/design_principles/des_princ.xml
+++ b/system/doc/design_principles/des_princ.xml
@@ -4,7 +4,7 @@
-
+
Simplified one_for_one Supervisors
A supervisor with restart strategy simple_one_for_one is
a simplified one_for_one supervisor, where all child
--
cgit v1.2.3
From f06f69068807168cf4cc731711ed82489cc5b99c Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Mon, 25 Apr 2016 12:07:32 +0200
Subject: Add section on state filtering
Misc documentation fixes.
---
system/doc/design_principles/code_lock.dia | Bin 2968 -> 2955 bytes
system/doc/design_principles/code_lock.png | Bin 58906 -> 58823 bytes
system/doc/design_principles/statem.xml | 209 +++++++++++++++++++++--------
3 files changed, 152 insertions(+), 57 deletions(-)
(limited to 'system')
diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia
index d8262138a3..bed6d8ee86 100644
Binary files a/system/doc/design_principles/code_lock.dia and b/system/doc/design_principles/code_lock.dia differ
diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png
index 5e5744ecc2..e40f0320aa 100644
Binary files a/system/doc/design_principles/code_lock.png and b/system/doc/design_principles/code_lock.png differ
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index a921a40243..a4b8fb06a0 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -135,7 +135,7 @@ handle_event(EventType, EventContent, State, Data) ->
- Choosing Callback Mode
+ Choosing the Callback Mode
The two
callback modes
@@ -157,7 +157,7 @@ handle_event(EventType, EventContent, State, Data) ->
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
- hence focus on one state at the time.
+ hence to focus on one state at the time.
This mode fits well when you have a regular state diagram
@@ -216,7 +216,7 @@ handle_event(EventType, EventContent, State, Data) ->
it is is wrong, we start all over,
waiting for a new button sequence.
-
+
Code lock state diagram
@@ -842,9 +842,9 @@ open(cast, {button,_}, Data) ->
changed i.e OldState =/= NewState.
- Postponing is ordered by the
+ Postponing is ordered by the state transition
- state transition action
+ action
postpone.
@@ -951,9 +951,9 @@ do_unlock() ->
passing non-system messages to the callback module.
- The
+ The state transition
- state transition action
+ action
postpone is designed to be able to model
selective receives. A selective receive implicitly postpones
@@ -974,9 +974,10 @@ do_unlock() ->
Self Generated Events
It may be beneficial in some cases to be able to generate events
- to your own state machine. This can be done with the
+ to your own state machine.
+ This can be done with the state transition
- state transition action
+ action
{next_event,EventType,EventContent}.
@@ -1054,7 +1055,7 @@ enter(Tag, State, Data) ->
and some more utilizing the entry actions,
which deserves a new state diagram:
-
+
Code lock state diagram revisited
@@ -1200,6 +1201,71 @@ 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.
+ This state contains both the code lock code
+ and which digits that remains 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.
+
+
+ Another reason to filter the state can be
+ that the state is too big to print out since it fills
+ the error log with uninteresting details.
+
+
+ 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
+
+ Module:format_status/2
+
+ function, for example like this:
+
+
+ StateData =
+ {State,
+ maps:filter(
+ fun (code, _) -> false;
+ (remaining, _) -> false;
+ (_, _) -> true
+ end,
+ Data)},
+ case Opt of
+ terminate ->
+ StateData;
+ normal ->
+ [{data,[{"State",StateData}]}]
+ end.
+ ]]>
+
+ It is not mandatory to implement a
+
+ 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}.
+
+
+
@@ -1260,7 +1326,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
where StateName is as before
and LockButton is the current lock button:
-
-export([start_link/2,stop/0]).
-export([button/1,code_length/0,set_lock_button/1]).
--export([init/1,terminate/3,code_change/4]).
+-export([init/1,terminate/3,code_change/4,format_status/2]).
-export([handle_event/4]).
start_link(Code, LockButton) ->
@@ -1286,51 +1352,65 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
- Data = #{code => Code},
+ Data = #{code => Code, remaining => undefined, timer => undefined},
enter(?CALLBACK_MODE, {locked,LockButton}, Data, []).
-%% State: locked
-handle_event(internal, enter, {locked,_}, #{code := Code} = Data) ->
- do_lock(),
- {keep_state,Data#{remaining => Code}};
handle_event(
- {call,From}, {button,Digit}, {locked,LockButton},
- #{code := Code, remaining := Remaining} = Data) ->
- case Remaining of
- [Digit] -> % Complete
- enter(next_state, {open,LockButton}, Data, [{reply,From,ok}]);
- [Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest},[{reply,From,ok}]};
- [_|_] -> % Wrong
- {keep_state,Data#{remaining := Code},[{reply,From,ok}]}
- end;
-%%
-%% State: open
-handle_event(internal, enter, {open,_}, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
- do_unlock(),
- {keep_state,Data#{timer => Tref}};
+ {call,From}, {set_lock_button,NewLockButton},
+ {StateName,OldLockButton}, Data) ->
+ {next_state,{StateName,NewLockButton},Data,
+ [{reply,From,OldLockButton}]};
handle_event(
- info, {timeout,Tref,lock}, {open,LockButton},
- #{timer := Tref} = Data) ->
- enter(next_state, {locked,LockButton}, Data, []);
+ {call,From}, code_length,
+ {_StateName,_LockButton}, #{code := Code}) ->
+ {keep_state_and_data,
+ [{reply,From,length(Code)}]};
handle_event(
- {call,From}, {button,LockButton}, {open,LockButton},
- #{timer := Tref} = Data) ->
- erlang:cancel_timer(Tref),
- enter(next_state, {locked,LockButton}, Data, [{reply,From,locked}]);
-handle_event({call,_}, {button,_}, {open,_}, _) ->
- {keep_state_and_data,[postpone]};
-%%
-%% Any state
+ EventType, EventContent,
+ {locked,LockButton}, #{code := Code, remaining := Remaining} = Data) ->
+ case {EventType,EventContent} of
+ {internal,enter} ->
+ do_lock(),
+ {keep_state,Data#{remaining := Code}};
+ {{call,From},{button,Digit}} ->
+ case Remaining of
+ [Digit] -> % Complete
+ next_state(
+ {open,LockButton}, Data,
+ [{reply,From,ok}]);
+ [Digit|Rest] -> % Incomplete
+ {keep_state,Data#{remaining := Rest},
+ [{reply,From,ok}]};
+ [_|_] -> % Wrong
+ {keep_state,Data#{remaining := Code},
+ [{reply,From,ok}]}
+ end
+ end;
handle_event(
- {call,From}, {set_lock_button,NewLockButton}, {StateName,OldLockButton},
- Data) ->
- {next_state,{StateName,NewLockButton},Data,
- [{reply,From,OldLockButton}]};
-handle_event({call,From}, code_length, _State, #{code := Code}) ->
- {keep_state_and_data,[{reply,From,length(Code)}]}.
+ EventType, EventContent,
+ {open,LockButton}, #{timer := Timer} = Data) ->
+ case {EventType,EventContent} of
+ {internal,enter} ->
+ Tref = erlang:start_timer(10000, self(), lock),
+ do_unlock(),
+ {keep_state,Data#{timer := Tref}};
+ {info,{timeout,Timer,lock}} ->
+ next_state({locked,LockButton}, Data, []);
+ {{call,From},{button,Digit}} ->
+ if
+ Digit =:= LockButton ->
+ erlang:cancel_timer(Timer),
+ next_state(
+ {locked,LockButton}, Data,
+ [{reply,From,locked}]);
+ true ->
+ {keep_state_and_data,
+ [postpone]}
+ end
+ end.
+next_state(State, Data, Actions) ->
+ enter(next_state, State, Data, Actions).
enter(Tag, State, Data, Actions) ->
{Tag,State,Data,[{next_event,internal,enter}|Actions]}.
@@ -1344,13 +1424,28 @@ terminate(_Reason, State, _Data) ->
ok.
code_change(_Vsn, State, Data, _Extra) ->
{?CALLBACK_MODE,State,Data}.
- ]]>
-
- 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.
-
+format_status(Opt, [_PDict,State,Data]) ->
+ StateData =
+ {State,
+ maps:filter(
+ fun (code, _) -> false;
+ (remaining, _) -> false;
+ (_, _) -> true
+ end,
+ Data)},
+ case Opt of
+ terminate ->
+ StateData;
+ normal ->
+ [{data,[{"State",StateData}]}]
+ 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.
+
--
cgit v1.2.3