aboutsummaryrefslogtreecommitdiffstats
path: root/system/doc/design_principles
diff options
context:
space:
mode:
Diffstat (limited to 'system/doc/design_principles')
-rw-r--r--system/doc/design_principles/applications.xml160
-rw-r--r--system/doc/design_principles/code_lock.diabin2932 -> 2945 bytes
-rw-r--r--system/doc/design_principles/code_lock.pngbin59160 -> 59827 bytes
-rw-r--r--system/doc/design_principles/code_lock_2.diabin2621 -> 2956 bytes
-rw-r--r--system/doc/design_principles/code_lock_2.pngbin48927 -> 55553 bytes
-rw-r--r--system/doc/design_principles/fsm.xml338
-rw-r--r--system/doc/design_principles/part.xml3
-rw-r--r--system/doc/design_principles/spec_proc.xml88
-rw-r--r--system/doc/design_principles/statem.xml959
-rw-r--r--system/doc/design_principles/sup_princ.xml72
-rw-r--r--system/doc/design_principles/xmlfiles.mk3
11 files changed, 909 insertions, 714 deletions
diff --git a/system/doc/design_principles/applications.xml b/system/doc/design_principles/applications.xml
index 0a1b65ea8e..c673fde07e 100644
--- a/system/doc/design_principles/applications.xml
+++ b/system/doc/design_principles/applications.xml
@@ -11,7 +11,7 @@
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
@@ -19,7 +19,7 @@
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.
-
+
</legalnotice>
<title>Applications</title>
@@ -172,31 +172,136 @@ ch_app:stop([])</code>
</section>
<section>
- <marker id="app_dir"></marker>
- <title>Directory Structure</title>
- <p>When packaging code using <c>systools</c>, the code for each
- application is placed in a separate directory,
- <c>lib/Application-Vsn</c>, where <c>Vsn</c> is the version number.</p>
- <p>This can be useful to know, even if <c>systools</c> is not used,
- since Erlang/OTP is packaged according to the OTP principles
- and thus comes with this directory structure. The code server
- (see the <c>code(3)</c> manual page in Kernel) automatically
- uses code from
- the directory with the highest version number, if more than one
- version of an application is present.</p>
- <p>The application directory structure can also be used in the
- development environment. The version number can then
- be omitted from the name.</p>
- <p>The application directory has the following sub-directories:</p>
- <list type="bulleted">
- <item><c>src</c> - Contains the Erlang source code.</item>
- <item><c>ebin</c> - Contains the Erlang object code, the
- <c>beam</c> files. The <c>.app</c> file is also placed here.</item>
- <item><c>priv</c> - Used for application specific files. For
- example, C executables are placed here. The function
- <c>code:priv_dir/1</c> is to be used to access this directory.</item>
- <item><c>include</c> - Used for include files.</item>
- </list>
+ <marker id="app_dir"></marker>
+ <title>Directory Structure</title>
+ <p>When packaging code using <c>systools</c>, the code for each
+ application is placed in a separate directory,
+ <c>lib/Application-Vsn</c>, where <c>Vsn</c> is the version number.</p>
+ <p>This can be useful to know, even if <c>systools</c> is not used,
+ since Erlang/OTP is packaged according to the OTP principles
+ and thus comes with a specific directory structure. The code server
+ (see the <seealso marker="kernel:code"><c>code(3)</c></seealso> manual
+ page in Kernel) automatically uses code from
+ the directory with the highest version number, if more than one
+ version of an application is present.</p>
+ <section>
+ <title>Directory Structure guidelines for a Development Environment</title>
+ <p>Any directory structure for development will suffice as long as the released directory structure
+ adhere to the <seealso marker="#app_dir_released">description below</seealso>,
+ but it is encouraged that the same directory structure
+ also be used in a development environment. The version number should be omitted from the
+ application directory name since this is an artifact of the release step.
+ </p>
+ <p> Some sub-directories are <em>required</em>. Some sub-directories are <em>optional</em>, meaning that it should
+ only be used if the application itself requires it. Finally, some sub-directories are <em>recommended</em>,
+ meaning it is encouraged that it is used and used as described here. For example, both documentation
+ and tests are encouraged to exist in an application for it to be deemed a proper OTP application.</p>
+<code type="none">
+ ─ ${application}
+   ├── doc
+ │   ├── internal
+ │   ├── examples
+ │   └── src
+   ├── include
+   ├── priv
+   ├── src
+ │   └── ${application}.app.src
+   └── test
+</code>
+ <list type="bulleted">
+ <item><c>src</c> - Required. Contains the Erlang source code, the source of the <c>.app</c> file
+ and internal include files used by the application itself. Additional sub-directories within
+ <c>src</c> can be used as namespaces to organize source files. These directories should never
+ be deeper than one level.</item>
+ <item><c>priv</c> - Optional. Used for application specific files. </item>
+ <item><c>include</c> - Optional. Used for public include files that must be reachable from
+ other applications.</item>
+ <item><c>doc</c> - Recommended. Any source documentation should be placed in sub-directories here.</item>
+ <item><c>doc/internal</c> - Recommended. Any documentation that describes implementation details about
+ this application, not intended for publication, should be placed here.</item>
+ <item><c>doc/examples</c> - Recommended. Source code for examples on how to use this application should
+ be placed here. It is encouraged that examples are sourced to the public documentation from
+ this directory.</item>
+ <item><c>doc/src</c> - Recommended. All source files for documentation, such as Markdown, AsciiDoc or
+ XML-files, should be placed here.</item>
+ <item><c>test</c> - Recommended. All files regarding tests, such as test suites and test specifications,
+ should be placed here. </item>
+ </list>
+
+ <p>Other directories in the development environment may be needed. If source code from languages other
+ than Erlang is used, for instance C-code for NIFs, that code should be placed in a separate directory.
+ By convention it is recommended to prefix such directories with the language name, for example
+ <c>c_src</c> for C, <c>java_src</c> for Java or <c>go_src</c> for Go. Directories with <c>_src</c>
+ suffix indicates that it is a part of the application and the compilation step. The final build artifacts
+ should target the <c>priv/lib</c> or <c>priv/bin</c> directories.</p>
+ <p>The <c>priv</c> directory holds assets that the application needs during runtime. Executables should
+ reside in <c>priv/bin</c> and dynamically-linked libraries should reside in <c>priv/lib</c>. Other assets
+ are free to reside within the <c>priv</c> directory but it is recommended it does so in a structured manner.</p>
+ <p>Source files from other languages that generate Erlang code, such as ASN.1 or Mibs, should be placed
+ in directories, at the top level or in <c>src</c>, with the same name as the source language, for example
+ <c>asn1</c> and <c>mibs</c>. Build artifacts should be placed in their respective language directory,
+ such as <c>src</c> for Erlang code or <c>java_src</c> for Java code.</p>
+ <p>The <c>.app</c> file for release may reside in the <c>ebin</c>-directory in a development environment
+ but it is encouraged that this is an artifact of the build step. By convention a <c>.app.src</c> file
+ is used, which resides in the <c>src</c> directory. This file is nearly identical as the
+ <c>.app</c> file but certain fields may be replaced during the build step, such as the application version.</p>
+ <p>Directory names should not be capitalized.</p>
+ <p>It is encouraged to omit empty directories.</p>
+
+ </section>
+
+ <section>
+ <marker id="app_dir_released"></marker>
+ <title>Directory Structure for a Released System</title>
+ <p>A released application must follow a certain structure.
+ </p>
+<code type="none">
+ ─ ${application}-${version}
+   ├── bin
+   ├── doc
+ │   ├── html
+ │   ├── man[1-9]
+ │   ├── pdf
+ │   ├── internal
+ │   └── examples
+   ├── ebin
+ │   └── ${application}.app
+   ├── include
+   ├── priv
+ │   ├── lib
+ │   └── bin
+   └── src
+</code>
+ <list type="bulleted">
+ <item><c>src</c> - Optional. Contains the Erlang source code and internal include files
+ used by the application itself. This directory is no longer required in a released application.</item>
+ <item><c>ebin</c> - Required. Contains the Erlang object code, the <c>beam</c> files.
+ The <c>.app</c> file must also be placed here.</item>
+ <item><c>priv</c> - Optional. Used for application specific files. <c>code:priv_dir/1</c>
+ is to be used to access this directory.</item>
+ <item><c>priv/lib</c> - Recommended. Any shared-object files that are used by the application,
+ such as NIFs or linked-in-drivers, should be placed here.</item>
+ <item><c>priv/bin</c> - Recommended. Any executable that is used by the application,
+ such as port-programs, should be placed here.</item>
+ <item><c>include</c> - Optional. Used for public include files that must be reachable from
+ other applications.</item>
+ <item><c>bin</c> - Optional. Any executable that is a product of the application,
+ such as escripts or shell-scripts, should be placed here.</item>
+ <item><c>doc</c> - Optional. Any released documentation should be placed in
+ sub-directories here.</item>
+ <item><c>doc/man1</c> - Recommended. Man pages for Application executables.</item>
+ <item><c>doc/man3</c> - Recommended. Man pages for module APIs.</item>
+ <item><c>doc/man6</c> - Recommended. Man pages for Application overview.</item>
+ <item><c>doc/html</c> - Optional. HTML pages for the entire Application.</item>
+ <item><c>doc/pdf</c> - Optional. PDF documentation for the entire Application.</item>
+ </list>
+
+ <p>The <c>src</c> directory could be useful to release for debugging purposes but is not required.
+ The <c>include</c> directory should only be released if the applications has public include files.</p>
+ <p>The only documentation that is recommended to be released in this way are the man pages. HTML and PDF
+ will normally be distributed in some other manner.</p>
+ <p>It is encouraged to omit empty directories.</p>
+ </section>
</section>
<section>
@@ -381,4 +486,3 @@ application:start(Application, Type)</code>
<c>shutdown</c>, not <c>normal</c>.</p>
</section>
</chapter>
-
diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia
index 8e6ff8a898..eaa2aca5b0 100644
--- a/system/doc/design_principles/code_lock.dia
+++ b/system/doc/design_principles/code_lock.dia
Binary files differ
diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png
index 745fd91920..40bd35fc74 100644
--- a/system/doc/design_principles/code_lock.png
+++ b/system/doc/design_principles/code_lock.png
Binary files differ
diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia
index 142909a2f5..3b9ba554d8 100644
--- a/system/doc/design_principles/code_lock_2.dia
+++ b/system/doc/design_principles/code_lock_2.dia
Binary files differ
diff --git a/system/doc/design_principles/code_lock_2.png b/system/doc/design_principles/code_lock_2.png
index ecf7b0d799..3aca9dd5aa 100644
--- a/system/doc/design_principles/code_lock_2.png
+++ b/system/doc/design_principles/code_lock_2.png
Binary files differ
diff --git a/system/doc/design_principles/fsm.xml b/system/doc/design_principles/fsm.xml
deleted file mode 100644
index 4f2b75e6e8..0000000000
--- a/system/doc/design_principles/fsm.xml
+++ /dev/null
@@ -1,338 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>1997</year><year>2016</year>
- <holder>Ericsson AB. All Rights Reserved.</holder>
- </copyright>
- <legalnotice>
- 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.
-
- </legalnotice>
-
- <title>gen_fsm Behaviour</title>
- <prepared></prepared>
- <docno></docno>
- <date></date>
- <rev></rev>
- <file>fsm.xml</file>
- </header>
- <marker id="gen_fsm behaviour"></marker>
- <note>
- <p>
- There is a new behaviour
- <seealso marker="statem"><c>gen_statem</c></seealso>
- that is intended to replace <c>gen_fsm</c> for new code.
- It has the same features and add some really useful.
- This module will not be removed for the foreseeable future
- to keep old state machine implementations running.
- </p>
- </note>
- <p>This section is to be read with the <c>gen_fsm(3)</c> manual page
- in STDLIB, where all interface functions and callback
- functions are described in detail.</p>
-
- <section>
- <title>Finite-State Machines</title>
- <p>A Finite-State Machine (FSM) can be described as a set of
- relations of the form:</p>
- <pre>
-State(S) x Event(E) -> Actions(A), State(S')</pre>
- <p>These relations are interpreted as meaning:</p>
- <quote>
- <p>If we are in state <c>S</c> and event <c>E</c> occurs, we
- are to perform actions <c>A</c> and make a transition to
- state <c>S'</c>.</p>
- </quote>
- <p>For an FSM implemented using the <c>gen_fsm</c> behaviour,
- the state transition rules are written as a number of Erlang
- functions, which conform to the following convention:</p>
- <pre>
-StateName(Event, StateData) ->
- .. code for actions here ...
- {next_state, StateName', StateData'}</pre>
- </section>
-
- <section>
- <title>Example</title>
- <p>A door with a code lock can be viewed as an FSM. 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.</p>
- <p>If it is correct, the door is unlocked for 30 seconds (30,000 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.</p>
- <p>Implementing the code lock FSM using <c>gen_fsm</c> results in
- the following callback module:</p>
- <marker id="ex"></marker>
- <code type="none"><![CDATA[
--module(code_lock).
--behaviour(gen_fsm).
-
--export([start_link/1]).
--export([button/1]).
--export([init/1, locked/2, open/2]).
-
-start_link(Code) ->
- gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).
-
-button(Digit) ->
- gen_fsm:send_event(code_lock, {button, Digit}).
-
-init(Code) ->
- {ok, locked, {[], Code}}.
-
-locked({button, Digit}, {SoFar, Code}) ->
- case [Digit|SoFar] of
- Code ->
- do_unlock(),
- {next_state, open, {[], Code}, 30000};
- Incomplete when length(Incomplete)<length(Code) ->
- {next_state, locked, {Incomplete, Code}};
- _Wrong ->
- {next_state, locked, {[], Code}}
- end.
-
-open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.]]></code>
- <p>The code is explained in the next sections.</p>
- </section>
-
- <section>
- <title>Starting gen_fsm</title>
- <p>In the example in the previous section, the <c>gen_fsm</c> is
- started by calling <c>code_lock:start_link(Code)</c>:</p>
- <code type="none">
-start_link(Code) ->
- gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).
- </code>
- <p><c>start_link</c> calls the function <c>gen_fsm:start_link/4</c>,
- which spawns and links to a new process, a <c>gen_fsm</c>.</p>
- <list type="bulleted">
- <item>
- <p>The first argument, <c>{local, code_lock}</c>, specifies
- the name. In this case, the <c>gen_fsm</c> is locally
- registered as <c>code_lock</c>.</p>
- <p>If the name is omitted, the <c>gen_fsm</c> is not registered.
- Instead its pid must be used. The name can also be given
- as <c>{global, Name}</c>, in which case the <c>gen_fsm</c> is
- registered using <c>global:register_name/2</c>.</p>
- </item>
- <item>
- <p>The second argument, <c>code_lock</c>, is the name of
- the callback module, that is, the module where the callback
- functions are located.</p>
- <p>The interface functions (<c>start_link</c> and <c>button</c>)
- are then located in the same module as the callback
- functions (<c>init</c>, <c>locked</c>, and <c>open</c>). This
- is normally good programming practice, to have the code
- corresponding to one process contained in one module.</p>
- </item>
- <item>
- <p>The third argument, <c>Code</c>, is a list of digits that
- which is passed reversed to the callback function <c>init</c>.
- Here, <c>init</c>
- gets the correct code for the lock as indata.</p>
- </item>
- <item>
- <p>The fourth argument, <c>[]</c>, is a list of options. See
- the <c>gen_fsm(3)</c> manual page for available options.</p>
- </item>
- </list>
- <p>If name registration succeeds, the new <c>gen_fsm</c> process calls
- the callback function <c>code_lock:init(Code)</c>. This function
- is expected to return <c>{ok, StateName, StateData}</c>, where
- <c>StateName</c> is the name of the initial state of the
- <c>gen_fsm</c>. In this case <c>locked</c>, assuming the door is
- locked to begin with. <c>StateData</c> is the internal state of
- the <c>gen_fsm</c>. (For <c>gen_fsm</c>, the internal state is
- often referred to 'state data' to
- distinguish it from the state as in states of a state machine.)
- In this case, the state data is the button sequence so far (empty
- to begin with) and the correct code of the lock.</p>
- <code type="none">
-init(Code) ->
- {ok, locked, {[], Code}}.</code>
- <p><c>gen_fsm:start_link</c> is synchronous. It does not return until
- the <c>gen_fsm</c> has been initialized and is ready to
- receive notifications.</p>
- <p><c>gen_fsm:start_link</c> must be used if the <c>gen_fsm</c> is
- part of a supervision tree, that is, started by a supervisor. There
- is another function, <c>gen_fsm:start</c>, to start a standalone
- <c>gen_fsm</c>, that is, a <c>gen_fsm</c> that is not part of a
- supervision tree.</p>
- </section>
-
- <section>
- <title>Notifying about Events</title>
- <p>The function notifying the code lock about a button event is
- implemented using <c>gen_fsm:send_event/2</c>:</p>
- <code type="none">
-button(Digit) ->
- gen_fsm:send_event(code_lock, {button, Digit}).</code>
- <p><c>code_lock</c> is the name of the <c>gen_fsm</c> and must
- agree with the name used to start it.
- <c>{button, Digit}</c> is the actual event.</p>
- <p>The event is made into a message and sent to the <c>gen_fsm</c>.
- When the event is received, the <c>gen_fsm</c> calls
- <c>StateName(Event, StateData)</c>, which is expected to return a
- tuple <c>{next_state,StateName1,StateData1}</c>.
- <c>StateName</c> is the name of the current state and
- <c>StateName1</c> is the name of the next state to go to.
- <c>StateData1</c> is a new value for the state data of
- the <c>gen_fsm</c>.</p>
- <code type="none"><![CDATA[
-locked({button, Digit}, {SoFar, Code}) ->
- case [Digit|SoFar] of
- Code ->
- do_unlock(),
- {next_state, open, {[], Code}, 30000};
- Incomplete when length(Incomplete)<length(Code) ->
- {next_state, locked, {Incomplete, Code}};
- _Wrong ->
- {next_state, locked, {[], Code}};
- end.
-
-open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.]]></code>
- <p>If the door is locked and a button is pressed, the complete
- button sequence so far is compared with the correct code for
- the lock and, depending on the result, the door is either unlocked
- and the <c>gen_fsm</c> goes to state <c>open</c>, or the door
- remains in state <c>locked</c>.</p>
- </section>
-
- <section>
- <title>Time-Outs</title>
- <p>When a correct code has been given, the door is unlocked and
- the following tuple is returned from <c>locked/2</c>:</p>
- <code type="none">
-{next_state, open, {[], Code}, 30000};</code>
- <p>30,000 is a time-out value in milliseconds. After this time,
- that is, 30 seconds, a time-out occurs. Then,
- <c>StateName(timeout, StateData)</c> is called. The time-out
- then occurs when the door has been in state <c>open</c> for 30
- seconds. After that the door is locked again:</p>
- <code type="none">
-open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.</code>
- </section>
-
- <section>
- <title>All State Events</title>
- <p>Sometimes an event can arrive at any state of the <c>gen_fsm</c>.
- Instead of sending the message with <c>gen_fsm:send_event/2</c>
- and writing one clause handling the event for each state function,
- the message can be sent with <c>gen_fsm:send_all_state_event/2</c>
- and handled with <c>Module:handle_event/3</c>:</p>
- <code type="none">
--module(code_lock).
-...
--export([stop/0]).
-...
-
-stop() ->
- gen_fsm:send_all_state_event(code_lock, stop).
-
-...
-
-handle_event(stop, _StateName, StateData) ->
- {stop, normal, StateData}.</code>
- </section>
-
- <section>
- <title>Stopping</title>
-
- <section>
- <title>In a Supervision Tree</title>
- <p>If the <c>gen_fsm</c> is part of a supervision tree, no stop
- function is needed. The <c>gen_fsm</c> is automatically
- terminated by its supervisor. Exactly how this is done is
- defined by a
- <seealso marker="sup_princ#shutdown">shutdown strategy</seealso>
- set in the supervisor.</p>
- <p>If it is necessary to clean up before termination, the shutdown
- strategy must be a time-out value and the <c>gen_fsm</c> must be
- set to trap exit signals in the <c>init</c> function. When ordered
- to shutdown, the <c>gen_fsm</c> then calls the callback function
- <c>terminate(shutdown, StateName, StateData)</c>:</p>
- <code type="none">
-init(Args) ->
- ...,
- process_flag(trap_exit, true),
- ...,
- {ok, StateName, StateData}.
-
-...
-
-terminate(shutdown, StateName, StateData) ->
- ..code for cleaning up here..
- ok.</code>
- </section>
-
- <section>
- <title>Standalone gen_fsm</title>
- <p>If the <c>gen_fsm</c> is not part of a supervision tree, a stop
- function can be useful, for example:</p>
- <code type="none">
-...
--export([stop/0]).
-...
-
-stop() ->
- gen_fsm:send_all_state_event(code_lock, stop).
-...
-
-handle_event(stop, _StateName, StateData) ->
- {stop, normal, StateData}.
-
-...
-
-terminate(normal, _StateName, _StateData) ->
- ok.</code>
- <p>The callback function handling the <c>stop</c> event returns a
- tuple, <c>{stop,normal,StateData1}</c>, where <c>normal</c>
- specifies that it is a normal termination and <c>StateData1</c>
- is a new value for the state data of the <c>gen_fsm</c>. This
- causes the <c>gen_fsm</c> to call
- <c>terminate(normal,StateName,StateData1)</c> and then
- it terminates gracefully:</p>
- </section>
- </section>
-
- <section>
- <title>Handling Other Messages</title>
- <p>If the <c>gen_fsm</c> is to be able to receive other messages
- than events, the callback function
- <c>handle_info(Info, StateName, StateData)</c> must be implemented
- to handle them. Examples of
- other messages are exit messages, if the <c>gen_fsm</c> is linked to
- other processes (than the supervisor) and trapping exit signals.</p>
- <code type="none">
-handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
- ..code to handle exits here..
- {next_state, StateName1, StateData1}.</code>
- <p>The code_change method must also be implemented.</p>
- <code type="none">
-code_change(OldVsn, StateName, StateData, Extra) ->
- ..code to convert state (and more) during code change
- {ok, NextStateName, NewStateData}</code>
- </section>
-</chapter>
-
diff --git a/system/doc/design_principles/part.xml b/system/doc/design_principles/part.xml
index 6495211e04..899c7f2afe 100644
--- a/system/doc/design_principles/part.xml
+++ b/system/doc/design_principles/part.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1997</year><year>2016</year>
+ <year>1997</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -30,7 +30,6 @@
</header>
<xi:include href="des_princ.xml"/>
<xi:include href="gen_server_concepts.xml"/>
- <xi:include href="fsm.xml"/>
<xi:include href="statem.xml"/>
<xi:include href="events.xml"/>
<xi:include href="sup_princ.xml"/>
diff --git a/system/doc/design_principles/spec_proc.xml b/system/doc/design_principles/spec_proc.xml
index 5b156ac263..5f4e7ac685 100644
--- a/system/doc/design_principles/spec_proc.xml
+++ b/system/doc/design_principles/spec_proc.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2016</year>
+ <year>1997</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -45,61 +45,63 @@
<p>The <c>sys</c> module has functions for simple debugging of
processes implemented using behaviours. The <c>code_lock</c>
example from
- <seealso marker="fsm#ex">gen_fsm Behaviour</seealso>
+ <seealso marker="statem#Example">gen_statem Behaviour</seealso>
is used to illustrate this:</p>
<pre>
-% <input>erl</input>
-Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
+Erlang/OTP 20 [DEVELOPMENT] [erts-9.0] [source-5ace45e] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]
-Eshell V5.2.3.6 (abort with ^G)
-1> <input>code_lock:start_link([1,2,3,4]).</input>
-{ok,&lt;0.32.0>}
-2> <input>sys:statistics(code_lock, true).</input>
+Eshell V9.0 (abort with ^G)
+1> code_lock:start_link([1,2,3,4]).
+Lock
+{ok,&lt;0.63.0>}
+2> sys:statistics(code_lock, true).
ok
-3> <input>sys:trace(code_lock, true).</input>
+3> sys:trace(code_lock, true).
ok
-4> <input>code_lock:button(4).</input>
-*DBG* code_lock got event {button,4} in state closed
+4> code_lock:button(1).
+*DBG* code_lock receive cast {button,1} in state locked
ok
-*DBG* code_lock switched to state closed
-5> <input>code_lock:button(3).</input>
-*DBG* code_lock got event {button,3} in state closed
+*DBG* code_lock consume cast {button,1} in state locked
+5> code_lock:button(2).
+*DBG* code_lock receive cast {button,2} in state locked
ok
-*DBG* code_lock switched to state closed
-6> <input>code_lock:button(2).</input>
-*DBG* code_lock got event {button,2} in state closed
+*DBG* code_lock consume cast {button,2} in state locked
+6> code_lock:button(3).
+*DBG* code_lock receive cast {button,3} in state locked
ok
-*DBG* code_lock switched to state closed
-7> <input>code_lock:button(1).</input>
-*DBG* code_lock got event {button,1} in state closed
+*DBG* code_lock consume cast {button,3} in state locked
+7> code_lock:button(4).
+*DBG* code_lock receive cast {button,4} in state locked
ok
-OPEN DOOR
-*DBG* code_lock switched to state open
-*DBG* code_lock got event timeout in state open
-CLOSE DOOR
-*DBG* code_lock switched to state closed
-8> <input>sys:statistics(code_lock, get).</input>
-{ok,[{start_time,{{2003,6,12},{14,11,40}}},
- {current_time,{{2003,6,12},{14,12,14}}},
- {reductions,333},
+Unlock
+*DBG* code_lock consume cast {button,4} in state locked
+*DBG* code_lock receive state_timeout lock in state open
+Lock
+*DBG* code_lock consume state_timeout lock in state open
+8> sys:statistics(code_lock, get).
+{ok,[{start_time,{{2017,4,21},{16,8,7}}},
+ {current_time,{{2017,4,21},{16,9,42}}},
+ {reductions,2973},
{messages_in,5},
{messages_out,0}]}
-9> <input>sys:statistics(code_lock, false).</input>
+9> sys:statistics(code_lock, false).
ok
-10> <input>sys:trace(code_lock, false).</input>
+10> sys:trace(code_lock, false).
ok
-11> <input>sys:get_status(code_lock).</input>
-{status,&lt;0.32.0>,
- {module,gen_fsm},
- [[{'$ancestors',[&lt;0.30.0>]},
- {'$initial_call',{gen,init_it,
- [gen_fsm,&lt;0.30.0>,&lt;0.30.0>,
- {local,code_lock},
- code_lock,
- [1,2,3,4],
- []]}}],
- running,&lt;0.30.0>,[],
- [code_lock,closed,{[],[1,2,3,4]},code_lock,infinity]]}</pre>
+11> sys:get_status(code_lock).
+{status,&lt;0.63.0>,
+ {module,gen_statem},
+ [[{'$initial_call',{code_lock,init,1}},
+ {'$ancestors',[&lt;0.61.0>]}],
+ running,&lt;0.61.0>,[],
+ [{header,"Status for state machine code_lock"},
+ {data,[{"Status",running},
+ {"Parent",&lt;0.61.0>},
+ {"Logged Events",[]},
+ {"Postponed",[]}]},
+ {data,[{"State",
+ {locked,#{code => [1,2,3,4],remaining => [1,2,3,4]}}}]}]]}
+ </pre>
</section>
<section>
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index b63327291d..7febe31df3 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2016</year>
+ <year>2016</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -29,11 +29,11 @@
<rev></rev>
<file>statem.xml</file>
</header>
- <marker id="gen_statem behaviour"></marker>
+ <marker id="gen_statem Behaviour" />
<p>
This section is to be read with the
<seealso marker="stdlib:gen_statem"><c>gen_statem(3)</c></seealso>
- manual page in <c>STDLIB</c>, where all interface functions and callback
+ manual page in STDLIB, where all interface functions and callback
functions are described in detail.
</p>
<note>
@@ -50,9 +50,10 @@
<!-- =================================================================== -->
<section>
+ <marker id="Event-Driven State Machines" />
<title>Event-Driven State Machines</title>
<p>
- Established Automata theory does not deal much with
+ Established Automata Theory does not deal much with
how a state transition is triggered,
but assumes that the output is a function
of the input (and the state) and that they are
@@ -94,7 +95,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<!-- =================================================================== -->
<section>
- <marker id="callback_modes" />
+ <marker id="Callback Modes" />
<title>Callback Modes</title>
<p>
The <c>gen_statem</c> behavior supports two callback modes:
@@ -109,8 +110,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</p>
<pre>
StateName(EventType, EventContent, Data) ->
- .. code for actions here ...
- {next_state, NewStateName, NewData}.</pre>
+ ... code for actions here ...
+ {next_state, NewStateName, NewData}.
+ </pre>
+ <p>
+ This form is used in most examples here for example in section
+ <seealso marker="#Example">Example</seealso>.
+ </p>
</item>
<item>
<p>
@@ -120,8 +126,14 @@ StateName(EventType, EventContent, Data) ->
</p>
<pre>
handle_event(EventType, EventContent, State, Data) ->
- .. code for actions here ...
- {next_state, NewState, NewData}</pre>
+ ... code for actions here ...
+ {next_state, NewState, NewData}
+ </pre>
+ <p>
+ See section
+ <seealso marker="#One Event Handler">One Event Handler</seealso>
+ for an example.
+ </p>
</item>
</list>
<p>
@@ -134,10 +146,11 @@ handle_event(EventType, EventContent, State, Data) ->
</p>
<section>
+ <marker id="Choosing the Callback Mode" />
<title>Choosing the Callback Mode</title>
<p>
The two
- <seealso marker="#callback_modes">callback modes</seealso>
+ <seealso marker="#Callback Modes">callback modes</seealso>
give different possibilities
and restrictions, but one goal remains:
you want to handle all possible combinations of
@@ -195,10 +208,211 @@ handle_event(EventType, EventContent, State, Data) ->
<!-- =================================================================== -->
<section>
+ <marker id="State Enter Calls" />
+ <title>State Enter Calls</title>
+ <p>
+ The <c>gen_statem</c> behavior can regardless of callback mode
+ automatically
+ <seealso marker="stdlib:gen_statem#type-state_enter">
+ call the state callback
+ </seealso>
+ with special arguments whenever the state changes
+ so you can write state entry actions
+ near the rest of the state transition rules.
+ It typically looks like this:
+ </p>
+ <pre>
+StateName(enter, _OldState, Data) ->
+ ... code for state entry actions here ...
+ {keep_state, NewData};
+StateName(EventType, EventContent, Data) ->
+ ... code for actions here ...
+ {next_state, NewStateName, NewData}.</pre>
+ <p>
+ Depending on how your state machine is specified,
+ this can be a very useful feature,
+ but it forces you to handle the state enter calls in all states.
+ See also the
+ <seealso marker="#State Entry Actions">
+ State Entry Actions
+ </seealso>
+ chapter.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Actions" />
+ <title>Actions</title>
+ <p>
+ In the first section
+ <seealso marker="#Event-Driven State Machines">
+ Event-Driven State Machines
+ </seealso>
+ actions were mentioned as a part of
+ the general state machine model. These general actions
+ are implemented with the code that callback module
+ <c>gen_statem</c> executes in an event-handling
+ callback function before returning
+ to the <c>gen_statem</c> engine.
+ </p>
+ <p>
+ There are more specific state-transition actions
+ that a callback function can order the <c>gen_statem</c>
+ engine to do after the callback function return.
+ These are ordered by returning a list of
+ <seealso marker="stdlib:gen_statem#type-action">actions</seealso>
+ in the
+ <seealso marker="stdlib:gen_statem#type-state_callback_result">return tuple</seealso>
+ from the
+ <seealso marker="stdlib:gen_statem#Module:StateName/3">callback function</seealso>.
+ These state transition actions affect the <c>gen_statem</c>
+ engine itself and can do the following:
+ </p>
+ <list type="bulleted">
+ <item>
+ <seealso marker="stdlib:gen_statem#type-postpone">
+ Postpone
+ </seealso>
+ the current event, see section
+ <seealso marker="#Postponing Events">Postponing Events</seealso>
+ </item>
+ <item>
+ <seealso marker="stdlib:gen_statem#type-hibernate">
+ Hibernate
+ </seealso>
+ the <c>gen_statem</c>, treated in
+ <seealso marker="#Hibernation">Hibernation</seealso>
+ </item>
+ <item>
+ Start a
+ <seealso marker="stdlib:gen_statem#type-state_timeout">
+ state time-out</seealso>,
+ read more in section
+ <seealso marker="#State Time-Outs">State Time-Outs</seealso>
+ </item>
+ <item>
+ Start a
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ generic time-out</seealso>,
+ read more in section
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
+ </item>
+ <item>
+ Start an
+ <seealso marker="stdlib:gen_statem#type-event_timeout">event time-out</seealso>,
+ see more in section
+ <seealso marker="#Event Time-Outs">Event Time-Outs</seealso>
+ </item>
+ <item>
+ <seealso marker="stdlib:gen_statem#type-reply_action">
+ Reply
+ </seealso>
+ to a caller, mentioned at the end of section
+ <seealso marker="#All State Events">All State Events</seealso>
+ </item>
+ <item>
+ Generate the
+ <seealso marker="stdlib:gen_statem#type-action">
+ next event
+ </seealso>
+ to handle, see section
+ <seealso marker="#Self-Generated Events">Self-Generated Events</seealso>
+ </item>
+ </list>
+ <p>
+ For details, see the
+ <seealso marker="stdlib:gen_statem#type-action">
+ <c>gen_statem(3)</c>
+ </seealso>
+ manual page.
+ You can, for example, reply to many callers,
+ generate multiple next events,
+ and set time-outs to relative or absolute times.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Event Types" />
+ <title>Event Types</title>
+ <p>
+ Events are categorized in different
+ <seealso marker="stdlib:gen_statem#type-event_type">event types</seealso>.
+ Events of all types are handled in the same callback function,
+ for a given state, and the function gets
+ <c>EventType</c> and <c>EventContent</c> as arguments.
+ </p>
+ <p>
+ The following is a complete list of event types and where
+ they come from:
+ </p>
+ <taglist>
+ <tag><c>cast</c></tag>
+ <item>
+ Generated by
+ <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast</c></seealso>.
+ </item>
+ <tag><c>{call,From}</c></tag>
+ <item>
+ Generated by
+ <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>,
+ where <c>From</c> is the reply address to use
+ when replying either through the state transition action
+ <c>{reply,From,Msg}</c> or by calling
+ <seealso marker="stdlib:gen_statem#reply/1"><c>gen_statem:reply</c></seealso>.
+ </item>
+ <tag><c>info</c></tag>
+ <item>
+ Generated by any regular process message sent to
+ the <c>gen_statem</c> process.
+ </item>
+ <tag><c>state_timeout</c></tag>
+ <item>
+ Generated by state transition action
+ <seealso marker="stdlib:gen_statem#type-state_timeout">
+ <c>{state_timeout,Time,EventContent}</c>
+ </seealso>
+ state timer timing out.
+ </item>
+ <tag><c>{timeout,Name}</c></tag>
+ <item>
+ Generated by state transition action
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">
+ <c>{{timeout,Name},Time,EventContent}</c>
+ </seealso>
+ generic timer timing out.
+ </item>
+ <tag><c>timeout</c></tag>
+ <item>
+ Generated by state transition action
+ <seealso marker="stdlib:gen_statem#type-event_timeout">
+ <c>{timeout,Time,EventContent}</c>
+ </seealso>
+ (or its short form <c>Time</c>)
+ event timer timing out.
+ </item>
+ <tag><c>internal</c></tag>
+ <item>
+ Generated by state transition
+ <seealso marker="stdlib:gen_statem#type-action">action</seealso>
+ <c>{next_event,internal,EventContent}</c>.
+ All event types above can also be generated using
+ <c>{next_event,EventType,EventContent}</c>.
+ </item>
+ </taglist>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Example" />
<title>Example</title>
<p>
This example starts off as equivalent to the example in section
- <seealso marker="fsm"><c>gen_fsm</c> Behavior</seealso>.
+ <seealso marker="fsm"><c>gen_fsm</c>&nbsp;Behavior</seealso>.
In later sections, additions and tweaks are made
using features in <c>gen_statem</c> that <c>gen_fsm</c> does not have.
The end of this chapter provides the example again
@@ -221,16 +435,14 @@ handle_event(EventType, EventContent, State, Data) ->
This code lock state machine can be implemented using
<c>gen_statem</c> with the following callback module:
</p>
- <marker id="ex"></marker>
<code type="erl"><![CDATA[
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock).
--define(CALLBACK_MODE, state_functions).
-export([start_link/1]).
-export([button/1]).
--export([init/1,terminate/3,code_change/4]).
+-export([init/1,callback_mode/0,terminate/3,code_change/4]).
-export([locked/3,open/3]).
start_link(Code) ->
@@ -242,7 +454,10 @@ button(Digit) ->
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {?CALLBACK_MODE,locked,Data}.
+ {ok, locked, Data}.
+
+callback_mode() ->
+ state_functions.
locked(
cast, {button,Digit},
@@ -250,19 +465,19 @@ locked(
case Remaining of
[Digit] ->
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
- {next_state,locked,Data#{remaining := Rest}};
+ {next_state, locked, Data#{remaining := Rest}};
_Wrong ->
- {next_state,locked,Data#{remaining := Code}}
+ {next_state, locked, Data#{remaining := Code}}
end.
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
open(cast, {button,_}, Data) ->
- do_lock(),
- {next_state,locked,Data}.
+ {next_state, open, Data}.
do_lock() ->
io:format("Lock~n", []).
@@ -273,7 +488,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {?CALLBACK_MODE,State,Data}.
+ {ok, State, Data}.
]]></code>
<p>The code is explained in the next sections.</p>
</section>
@@ -281,6 +496,7 @@ code_change(_Vsn, State, Data, _Extra) ->
<!-- =================================================================== -->
<section>
+ <marker id="Starting gen_statem" />
<title>Starting gen_statem</title>
<p>
In the example in the previous section, <c>gen_statem</c> is
@@ -308,7 +524,7 @@ start_link(Code) ->
as <c>{global,Name}</c>, then the <c>gen_statem</c> is
registered using
<seealso marker="kernel:global#register_name/2"><c>global:register_name/2</c></seealso>
- in <c>Kernel</c>.
+ in Kernel.
</p>
</item>
<item>
@@ -343,14 +559,8 @@ start_link(Code) ->
<p>
If name registration succeeds, the new <c>gen_statem</c> process
calls callback function <c>code_lock:init(Code)</c>.
- This function is expected to return <c>{CallbackMode,State,Data}</c>,
- where
- <seealso marker="#callback_modes"><c>CallbackMode</c></seealso>
- selects callback module state function mode, in this case
- <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>
- through macro <c>?CALLBACK_MODE</c>. That is, each state
- has got its own handler function.
- <c>State</c> is the initial state of the <c>gen_statem</c>,
+ This function is expected to return <c>{ok, State, Data}</c>,
+ where <c>State</c> is the initial state of the <c>gen_statem</c>,
in this case <c>locked</c>; assuming that the door is locked to begin
with. <c>Data</c> is the internal server data of the <c>gen_statem</c>.
Here the server data is a <seealso marker="stdlib:maps">map</seealso>
@@ -359,11 +569,12 @@ start_link(Code) ->
that stores the remaining correct button sequence
(the same as the <c>code</c> to begin with).
</p>
+
<code type="erl"><![CDATA[
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {?CALLBACK_MODE,locked,Data}.
+ {ok,locked,Data}.
]]></code>
<p>Function
<seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso>
@@ -380,11 +591,27 @@ init(Code) ->
can be used to start a standalone <c>gen_statem</c>, that is,
a <c>gen_statem</c> that is not part of a supervision tree.
</p>
+
+ <code type="erl"><![CDATA[
+callback_mode() ->
+ state_functions.
+ ]]></code>
+ <p>
+ Function
+ <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ selects the
+ <seealso marker="#Callback Modes"><c>CallbackMode</c></seealso>
+ for the callback module, in this case
+ <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>.
+ That is, each state has got its own handler function.
+ </p>
+
</section>
<!-- =================================================================== -->
<section>
+ <marker id="Handling Events" />
<title>Handling Events</title>
<p>The function notifying the code lock about a button event is
implemented using
@@ -404,11 +631,13 @@ button(Digit) ->
The event is made into a message and sent to the <c>gen_statem</c>.
When the event is received, the <c>gen_statem</c> calls
<c>StateName(cast, Event, Data)</c>, which is expected to
- return a tuple <c>{next_state,NewStateName,NewData}</c>.
+ return a tuple <c>{next_state, NewStateName, NewData}</c>,
+ or <c>{next_state, NewStateName, NewData, Actions}</c>.
<c>StateName</c> is the name of the current state and
<c>NewStateName</c> is the name of the next state to go to.
<c>NewData</c> is a new value for the server data of
- the <c>gen_statem</c>.
+ the <c>gen_statem</c>, and <c>Actions</c> is a list of
+ actions on the <c>gen_statem</c> engine.
</p>
<code type="erl"><![CDATA[
locked(
@@ -417,19 +646,19 @@ locked(
case Remaining of
[Digit] -> % Complete
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
- {next_state,locked,Data#{remaining := Rest}};
+ {next_state, locked, Data#{remaining := Rest}};
[_|_] -> % Wrong
- {next_state,locked,Data#{remaining := Code}}
+ {next_state, locked, Data#{remaining := Code}}
end.
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
open(cast, {button,_}, Data) ->
- do_lock(),
- {next_state,locked,Data}.
+ {next_state, open, Data}.
]]></code>
<p>
If the door is locked and a button is pressed, the pressed
@@ -443,38 +672,55 @@ open(cast, {button,_}, Data) ->
restarts from the start of the code sequence.
</p>
<p>
- In state <c>open</c>, any button locks the door, as
- any event cancels the event timer, so no
- time-out event occurs after a button event.
+ If the whole code is correct, the server changes states
+ to <c>open</c>.
+ </p>
+ <p>
+ In state <c>open</c>, a button event is ignored
+ by staying in the same state. This can also be done
+ by returning <c>{keep_state, Data}</c> or in this case
+ since <c>Data</c> unchanged even by returning
+ <c>keep_state_and_data</c>.
</p>
</section>
<section>
- <title>Event Time-Outs</title>
+ <marker id="State Time-Outs" />
+ <title>State Time-Outs</title>
<p>
When a correct code has been given, the door is unlocked and
the following tuple is returned from <c>locked/2</c>:
</p>
<code type="erl"><![CDATA[
-{next_state,open,Data#{remaining := Code},10000};
+{next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}]};
]]></code>
<p>
10,000 is a time-out value in milliseconds.
After this time (10 seconds), a time-out occurs.
- Then, <c>StateName(timeout, 10000, Data)</c> is called.
+ Then, <c>StateName(state_timeout, lock, Data)</c> is called.
The time-out occurs when the door has been in state <c>open</c>
for 10 seconds. After that the door is locked again:
</p>
<code type="erl"><![CDATA[
-open(timeout, _, Data) ->
+open(state_timeout, lock, Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state, locked, Data};
]]></code>
+ <p>
+ The timer for a state time-out is automatically cancelled
+ when the state machine changes states. You can restart
+ a state time-out by setting it to a new time, which cancels
+ the running timer and starts a new. This implies that
+ you can cancel a state time-out by restarting it with
+ time <c>infinity</c>.
+ </p>
</section>
<!-- =================================================================== -->
<section>
+ <marker id="All State Events" />
<title>All State Events</title>
<p>
Sometimes events can arrive in any state of the <c>gen_statem</c>.
@@ -507,21 +753,24 @@ 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)}]}.
+ {keep_state, Data, [{reply,From,length(Code)}]}.
]]></code>
<p>
This example uses
<seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call/2</c></seealso>,
which waits for a reply from the server.
The reply is sent with a <c>{reply,From,Reply}</c> tuple
- in an action list in the <c>{keep_state,...}</c> tuple
- that retains the current state.
+ in an action list in the <c>{keep_state, ...}</c> tuple
+ that retains the current state. This return form is convenient
+ when you want to stay in the current state but do not know or
+ care about what it is.
</p>
</section>
<!-- =================================================================== -->
<section>
+ <marker id="One Event Handler" />
<title>One Event Handler</title>
<p>
If mode <c>handle_event_function</c> is used,
@@ -533,12 +782,11 @@ handle_event({call,From}, code_length, #{code := Code} = Data) ->
</p>
<code type="erl"><![CDATA[
...
--define(CALLBACK_MODE, handle_event_function).
-
-...
-export([handle_event/4]).
...
+callback_mode() ->
+ handle_event_function.
handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
case State of
@@ -546,19 +794,19 @@ handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
case maps:get(remaining, Data) of
[Digit] -> % Complete
do_unlock(),
- {next_state,open,Data#{remaining := Code},10000};
+ {next_state, open, Data#{remaining := Code},
+ [{state_timeout,10000,lock}]};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {keep_state, Data#{remaining := Code}}
end;
open ->
- do_lock(),
- {next_state,locked,Data}
+ keep_state_and_data
end;
-handle_event(timeout, _, open, Data) ->
+handle_event(state_timeout, lock, open, Data) ->
do_lock(),
- {next_state,locked,Data}.
+ {next_state, locked, Data}.
...
]]></code>
@@ -567,9 +815,11 @@ handle_event(timeout, _, open, Data) ->
<!-- =================================================================== -->
<section>
+ <marker id="Stopping" />
<title>Stopping</title>
<section>
+ <marker id="In a Supervision Tree" />
<title>In a Supervision Tree</title>
<p>
If the <c>gen_statem</c> is part of a supervision tree,
@@ -597,7 +847,7 @@ init(Args) ->
callback function <c>terminate(shutdown, State, Data)</c>.
</p>
<p>
- In the following example, function <c>terminate/3</c>
+ In this example, function <c>terminate/3</c>
locks the door if it is open, so we do not accidentally leave the door
open when the supervision tree terminates:
</p>
@@ -609,6 +859,7 @@ terminate(_Reason, State, _Data) ->
</section>
<section>
+ <marker id="Standalone gen_statem" />
<title>Standalone gen_statem</title>
<p>
If the <c>gen_statem</c> is not part of a supervision tree,
@@ -635,127 +886,132 @@ stop() ->
<!-- =================================================================== -->
<section>
- <title>Actions</title>
+ <marker id="Event Time-Outs" />
+ <title>Event Time-Outs</title>
<p>
- In the first sections actions were mentioned as a part of
- the general state machine model. These general actions
- are implemented with the code that callback module
- <c>gen_statem</c> executes in an event-handling
- callback function before returning
- to the <c>gen_statem</c> engine.
+ A time-out feature inherited from <c>gen_statem</c>'s predecessor
+ <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>,
+ is an event time-out, that is,
+ if an event arrives the timer is cancelled.
+ You get either an event or a time-out, but not both.
</p>
<p>
- There are more specific state-transition actions
- that a callback function can order the <c>gen_statem</c>
- engine to do after the callback function return.
- These are ordered by returning a list of
- <seealso marker="stdlib:gen_statem#type-action">actions</seealso>
- in the
- <seealso marker="stdlib:gen_statem#type-state_function_result">return tuple</seealso>
- from the
- <seealso marker="stdlib:gen_statem#Module:StateName/3">callback function</seealso>.
- These state transition actions affect the <c>gen_statem</c>
- engine itself and can do the following:
+ It is ordered by the state transition action
+ <c>{timeout,Time,EventContent}</c>, or just <c>Time</c>,
+ or even just <c>Time</c> instead of an action list
+ (the latter is a form inherited from <c>gen_fsm</c>.
</p>
- <list type="bulleted">
- <item>Postpone the current event</item>
- <item>Hibernate the <c>gen_statem</c></item>
- <item>Start an event time-out</item>
- <item>Reply to a caller</item>
- <item>Generate the next event to handle</item>
- </list>
<p>
- In the example earlier was mentioned the event time-out
- and replying to a caller.
- An example of event postponing is included later in this chapter.
- For details, see the
- <seealso marker="stdlib:gen_statem#type-action"><c>gen_statem(3)</c></seealso>
- manual page.
- You can, for example, reply to many callers
- and generate multiple next events to handle.
+ This type of time-out is useful to for example act on inactivity.
+ Let us restart the code sequence
+ if no button is pressed for say 30 seconds:
</p>
- </section>
-
-<!-- =================================================================== -->
+ <code type="erl"><![CDATA[
+...
- <section>
- <title>Event Types</title>
+locked(
+ timeout, _,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {next_state, locked, Data#{remaining := Code}};
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+...
+ [Digit|Rest] -> % Incomplete
+ {next_state, locked, Data#{remaining := Rest}, 30000};
+...
+ ]]></code>
<p>
- The previous sections mentioned a few
- <seealso marker="stdlib:gen_statem#type-event_type">event types</seealso>.
- Events of all types are handled in the same callback function,
- for a given state, and the function gets
- <c>EventType</c> and <c>EventContent</c> as arguments.
+ Whenever we receive a button event we start an event time-out
+ of 30 seconds, and if we get an event type <c>timeout</c>
+ we reset the remaining code sequence.
</p>
<p>
- The following is a complete list of event types and where
- they come from:
+ An event time-out is cancelled by any other event so you either
+ get some other event or the time-out event. It is therefore
+ not possible nor needed to cancel or restart an event time-out.
+ Whatever event you act on has already cancelled
+ the event time-out...
</p>
- <taglist>
- <tag><c>cast</c></tag>
- <item>
- Generated by
- <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast</c></seealso>.
- </item>
- <tag><c>{call,From}</c></tag>
- <item>
- Generated by
- <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>,
- where <c>From</c> is the reply address to use
- when replying either through the state transition action
- <c>{reply,From,Msg}</c> or by calling
- <seealso marker="stdlib:gen_statem#reply/1"><c>gen_statem:reply</c></seealso>.
- </item>
- <tag><c>info</c></tag>
- <item>
- Generated by any regular process message sent to
- the <c>gen_statem</c> process.
- </item>
- <tag><c>timeout</c></tag>
- <item>
- Generated by state transition action
- <c>{timeout,Time,EventContent}</c> (or its short form <c>Time</c>)
- timer timing out.
- </item>
- <tag><c>internal</c></tag>
- <item>
- Generated by state transition action
- <c>{next_event,internal,EventContent}</c>.
- All event types above can also be generated using
- <c>{next_event,EventType,EventContent}</c>.
- </item>
- </taglist>
</section>
<!-- =================================================================== -->
<section>
- <title>State Time-Outs</title>
+ <marker id="Generic Time-Outs" />
+ <title>Generic Time-Outs</title>
<p>
- The time-out event generated by state transition action
- <c>{timeout,Time,EventContent}</c> is an event time-out,
- that is, if an event arrives the timer is cancelled.
- You get either an event or a time-out, but not both.
+ The previous example of state time-outs only work if
+ the state machine stays in the same state during the
+ time-out time. And event time-outs only work if no
+ disturbing unrelated events occur.
+ </p>
+ <p>
+ You may want to start a timer in one state and respond
+ to the time-out in another, maybe cancel the time-out
+ without changing states, or perhaps run multiple
+ time-outs in parallel. All this can be accomplished with
+ <seealso marker="stdlib:gen_statem#type-generic_timeout">generic time-outs</seealso>.
+ They may look a little bit like
+ <seealso marker="stdlib:gen_statem#type-event_timeout">event time-outs</seealso>
+ but contain a name to allow for any number of them simultaneously
+ and they are not automatically cancelled.
</p>
<p>
- 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 time-out in another. This can be accomplished
- with a regular Erlang timer:
- <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer</c></seealso>.
+ Here is how to accomplish the state time-out
+ in the previous example by instead using a generic time-out
+ named <c>open_tm</c>:
</p>
+ <code type="erl"><![CDATA[
+...
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] ->
+ do_unlock(),
+ {next_state, open, Data#{remaining := Code},
+ [{{timeout,open_tm},10000,lock}]};
+...
+
+open({timeout,open_tm}, lock, Data) ->
+ do_lock(),
+ {next_state,locked,Data};
+open(cast, {button,_}, Data) ->
+ {keep_state,Data};
+...
+ ]]></code>
+ <p>
+ Just as
+ <seealso marker="#State Time-Outs">state time-outs</seealso>
+ you can restart or cancel a specific generic time-out
+ by setting it to a new time or <c>infinity</c>.
+ </p>
+ <p>
+ Another way to handle a late time-out can be to not cancel it,
+ but to ignore it if it arrives in a state
+ where it is known to be late.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Erlang Timers" />
+ <title>Erlang Timers</title>
<p>
- For the example so far in this chapter: using the
- <c>gen_statem</c> event timer has the consequence that
- if a button event is generated while in the <c>open</c> state,
- the time-out is cancelled and the button event is delivered.
- So, we choose to lock the door if this occurred.
+ The most versatile way to handle time-outs is to use
+ Erlang Timers; see
+ <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer3,4</c></seealso>.
+ Most time-out tasks can be performed with the
+ time-out features in <c>gen_statem</c>,
+ but an example of one that can not is if you should need
+ the return value from
+ <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>, that is; the remaining time of the timer.
</p>
<p>
- Suppose that we do not want a button to lock the door,
- instead we want to ignore button events in the <c>open</c> state.
- Then we start a timer when entering the <c>open</c> state
- and waits for it to expire while ignoring button events:
+ Here is how to accomplish the state time-out
+ in the previous example by instead using an Erlang Timer:
</p>
<code type="erl"><![CDATA[
...
@@ -766,25 +1022,37 @@ locked(
[Digit] ->
do_unlock(),
Tref = erlang:start_timer(10000, self(), lock),
- {next_state,open,Data#{remaining := Code, timer := Tref}};
+ {next_state, open, Data#{remaining := Code, timer => Tref}};
...
open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
do_lock(),
- {next_state,locked,Data};
+ {next_state,locked,maps:remove(timer, Data)};
open(cast, {button,_}, Data) ->
{keep_state,Data};
...
]]></code>
<p>
+ Removing the <c>timer</c> key from the map when we
+ change to state <c>locked</c> is not strictly
+ necessary since we can only get into state <c>open</c>
+ with an updated <c>timer</c> map value. But it can be nice
+ to not have outdated values in the state <c>Data</c>!
+ </p>
+ <p>
If you need to cancel a timer because of some other event, you can use
<seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>.
- Notice that a time-out message cannot arrive after this,
- unless you have postponed it (see the next section) before,
+ Note that a time-out message cannot arrive after this,
+ unless you have postponed it before (see the next section),
so ensure that you do not accidentally postpone such messages.
+ Also note that a time-out message may have arrived
+ just before you cancelling it, so you may have to read out
+ such a message from the process mailbox depending on
+ the return value from
+ <seealso marker="erts:erlang#cancel_timer/2"><c>erlang:cancel_timer(Tref)</c></seealso>.
</p>
<p>
- Another way to cancel a timer is not to cancel it,
+ Another way to handle a late time-out can be to not cancel it,
but to ignore it if it arrives in a state
where it is known to be late.
</p>
@@ -793,6 +1061,7 @@ open(cast, {button,_}, Data) ->
<!-- =================================================================== -->
<section>
+ <marker id="Postponing Events" />
<title>Postponing Events</title>
<p>
If you want to ignore a particular event in the current state
@@ -817,20 +1086,28 @@ open(cast, {button,_}, Data) ->
...
]]></code>
<p>
- The fact that a postponed event is only retried after a state change
- translates into a requirement on the event and state space.
- If you have a choice between storing a state data item
- in the <c>State</c> or in the <c>Data</c>:
- if a change in the item value affects which events that
- are handled, then this item is to be part of the state.
+ Since a postponed event is only retried after a state change,
+ you have to think about where to keep a state data item.
+ You can keep it in the server <c>Data</c>
+ or in the <c>State</c> itself,
+ for example by having two more or less identical states
+ to keep a boolean value, or by using a complex state with
+ <seealso marker="#Callback Modes">callback mode</seealso>
+ <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>.
+ If a change in the value changes the set of events that is handled,
+ then the value should be kept in the State.
+ Otherwise no postponed events will be retried
+ since only the server Data changes.
</p>
<p>
- You want to avoid that you maybe much later decide
- to postpone an event in one state and by misfortune it is never retried,
- as the code only changes the <c>Data</c> but not the <c>State</c>.
+ This is not important if you do not postpone events.
+ But if you later decide to start postponing some events,
+ then the design flaw of not having separate states
+ when they should be, might become a hard to find bug.
</p>
<section>
+ <marker id="Fuzzy State Diagrams" />
<title>Fuzzy State Diagrams</title>
<p>
It is not uncommon that a state diagram does not specify
@@ -847,6 +1124,7 @@ open(cast, {button,_}, Data) ->
</section>
<section>
+ <marker id="Selective Receive" />
<title>Selective Receive</title>
<p>
Erlang's selective receive statement is often used to
@@ -926,6 +1204,70 @@ do_unlock() ->
<!-- =================================================================== -->
<section>
+ <marker id="State Entry Actions" />
+ <title>State Entry Actions</title>
+ <p>
+ Say you have a state machine specification
+ that uses state entry actions.
+ Allthough you can code this using self-generated events
+ (described in the next section), especially if just
+ one or a few states has got state entry actions,
+ this is a perfect use case for the built in
+ <seealso marker="#State Enter Calls">state enter calls</seealso>.
+ </p>
+ <p>
+ You return a list containing <c>state_enter</c> from your
+ <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>callback_mode/0</c></seealso>
+ function and the <c>gen_statem</c> engine will call your
+ state callback once with the arguments
+ <c>(enter, OldState, ...)</c> whenever the state changes.
+ Then you just need to handle these event-like calls in all states.
+ </p>
+ <code type="erl"><![CDATA[
+...
+init(Code) ->
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ {ok, locked, Data}.
+
+callback_mode() ->
+ [state_functions,state_enter].
+
+locked(enter, _OldState, Data) ->
+ do_lock(),
+ {keep_state,Data#{remaining => Code}};
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ case Remaining of
+ [Digit] ->
+ {next_state, open, Data};
+...
+
+open(enter, _OldState, _Data) ->
+ do_unlock(),
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+open(state_timeout, lock, Data) ->
+ {next_state, locked, Data};
+...
+ ]]></code>
+ <p>
+ You can repeat the state entry code by returning one of
+ <c>{repeat_state, ...}</c>, <c>{repeat_state_and_data,_}</c>
+ or <c>repeat_state_and_data</c> that otherwise behaves
+ exactly like their <c>keep_state</c> siblings.
+ See the type
+ <seealso marker="stdlib:gen_statem#type-state_callback_result">
+ <c>state_callback_result()</c>
+ </seealso>
+ in the reference manual.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="Self-Generated Events" />
<title>Self-Generated Events</title>
<p>
It can sometimes be beneficial to be able to generate events
@@ -943,67 +1285,71 @@ do_unlock() ->
from your state machine to itself.
</p>
<p>
- One example of using self-generated events can be when you have
- a state machine specification that uses state entry actions.
- You can code that using a dedicated function
- to do the state transition. But if you want that code to be
- visible besides the other state logic, you can insert
- an <c>internal</c> event that does the entry actions.
- This has the same unfortunate consequence as using
- state transition functions: everywhere you go to
- the state, you must explicitly
- insert the <c>internal</c> event
- or use a state transition function.
+ One example for this is to pre-process incoming data, for example
+ decrypting chunks or collecting characters up to a line break.
+ Purists may argue that this should be modelled with a separate
+ state machine that sends pre-processed events
+ to the main state machine.
+ But to decrease overhead the small pre-processing state machine
+ can be implemented in the common state event handling
+ of the main state machine using a few state data variables
+ that then sends the pre-processed events as internal events
+ to the main state machine.
</p>
<p>
- The following is an implementation of entry actions
- using <c>internal</c> events with content <c>enter</c>
- using a helper function <c>enter/3</c> for state entry:
+ The following example uses an input model where you give the lock
+ characters with <c>put_chars(Chars)</c> and then call
+ <c>enter()</c> to finish the input.
</p>
<code type="erl"><![CDATA[
...
--define(CALLBACK_MODE, state_functions).
-
+-export(put_chars/1, enter/0).
...
+put_chars(Chars) when is_binary(Chars) ->
+ gen_statem:call(?NAME, {chars,Chars}).
-init(Code) ->
- process_flag(trap_exit, true),
- Data = #{code => Code},
- enter(?CALLBACK_MODE, locked, Data).
+enter() ->
+ gen_statem:call(?NAME, enter).
...
-locked(internal, enter, _Data) ->
+locked(enter, _OldState, 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);
+ {keep_state,Data#{remaining => Code, buf => []}};
...
-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);
+handle_event({call,From}, {chars,Chars}, #{buf := Buf} = Data) ->
+ {keep_state, Data#{buf := [Chars|Buf],
+ [{reply,From,ok}]};
+handle_event({call,From}, enter, #{buf := Buf} = Data) ->
+ Chars = unicode:characters_to_binary(lists:reverse(Buf)),
+ try binary_to_integer(Chars) of
+ Digit ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,ok},
+ {next_event,internal,{button,Chars}}]}
+ catch
+ error:badarg ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,{error,not_an_integer}}]}
+ end;
...
-
-enter(Tag, State, Data) ->
- {Tag,State,Data,[{next_event,internal,enter}]}.
]]></code>
+ <p>
+ If you start this program with <c>code_lock:start([17])</c>
+ you can unlock with <c>code_lock:put_chars(&lt;&lt;"001">>),
+ code_lock:put_chars(&lt;&lt;"7">>), code_lock:enter()</c>.
+ </p>
</section>
<!-- =================================================================== -->
<section>
+ <marker id="Example Revisited" />
<title>Example Revisited</title>
<p>
- This section includes the example after all mentioned modifications
- and some more using the entry actions,
+ This section includes the example after most of the mentioned
+ modifications and some more using state enter calls,
which deserves a new state diagram:
</p>
<image file="../design_principles/code_lock_2.png">
@@ -1019,6 +1365,7 @@ enter(Tag, State, Data) ->
</p>
<section>
+ <marker id="Callback Mode: state_functions" />
<title>Callback Mode: state_functions</title>
<p>
Using state functions:
@@ -1027,11 +1374,10 @@ 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]).
--export([init/1,terminate/3,code_change/4]).
+-export([init/1,callback_mode/0,terminate/3,code_change/4]).
-export([locked/3,open/3]).
start_link(Code) ->
@@ -1047,40 +1393,44 @@ code_length() ->
init(Code) ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(?CALLBACK_MODE, locked, Data).
+ {ok, locked, Data}.
-locked(internal, enter, #{code := Code} = Data) ->
+callback_mode() ->
+ [state_functions,state_enter].
+
+locked(enter, _OldState, #{code := Code} = Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
+ {keep_state, Data#{remaining => Code}};
+locked(
+ timeout, _,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {keep_state, Data#{remaining := Code}};
locked(
cast, {button,Digit},
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] -> % Complete
- enter(next_state, open, Data);
+ {next_state, open, Data};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}, 30000};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {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),
+open(enter, _OldState, _Data) ->
do_unlock(),
- {keep_state,Data#{timer => Tref}};
-open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data);
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+open(state_timeout, lock, Data) ->
+ {next_state, locked, Data};
open(cast, {button,_}, _) ->
- {keep_state_and_data,[postpone]};
+ {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}]}.
+ {keep_state_and_data, [{reply,From,length(Code)}]}.
do_lock() ->
io:format("Locked~n", []).
@@ -1091,64 +1441,69 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {?CALLBACK_MODE,State,Data}.
+ {ok,State,Data}.
]]></code>
</section>
<section>
+ <marker id="Callback Mode: handle_event_function" />
<title>Callback Mode: handle_event_function</title>
<p>
This section describes what to change in the example
to use one <c>handle_event/4</c> function.
The previously used approach to first branch depending on event
- does not work that well here because of the generated
- entry actions, so this example first branches depending on state:
+ does not work that well here because of the state enter calls,
+ so this example first branches depending on state:
</p>
<code type="erl"><![CDATA[
...
--define(CALLBACK_MODE, handle_event_function).
-
-...
-export([handle_event/4]).
...
+callback_mode() ->
+ [handle_event_function,state_enter].
%% State: locked
-handle_event(internal, enter, locked, #{code := Code} = Data) ->
+handle_event(
+ enter, _OldState, locked,
+ #{code := Code} = Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
+ {keep_state, Data#{remaining => Code}};
+handle_event(
+ timeout, _, locked,
+ #{code := Code, remaining := Remaining} = Data) ->
+ {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);
+ {next_state, open, Data};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest}};
+ {keep_state, Data#{remaining := Rest}, 30000};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code}}
+ {keep_state, Data#{remaining := Code}}
end;
%%
%% State: open
-handle_event(internal, enter, open, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
+handle_event(enter, _OldState, open, _Data) ->
do_unlock(),
- {keep_state,Data#{timer => Tref}};
-handle_event(info, {timeout,Tref,lock}, open, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data);
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+handle_event(state_timeout, lock, open, Data) ->
+ {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)}]}.
+ {keep_state_and_data, [{reply,From,length(Code)}]}.
...
]]></code>
</section>
<p>
Notice that postponing buttons from the <c>locked</c> state
- to the <c>open</c> state feels like the wrong thing to do
+ to the <c>open</c> state feels like a strange thing to do
for a code lock, but it at least illustrates event postponing.
</p>
</section>
@@ -1156,6 +1511,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) ->
<!-- =================================================================== -->
<section>
+ <marker id="Filter the State" />
<title>Filter the State</title>
<p>
The example servers so far in this chapter
@@ -1208,26 +1564,30 @@ format_status(Opt, [_PDict,State,Data]) ->
<seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso>
function. If you do not, a default implementation is used that
does the same as this example function without filtering
- the <c>Data</c> term, that is, <c>StateData = {State,Data}</c>.
+ the <c>Data</c> term, that is, <c>StateData = {State,Data}</c>,
+ in this example containing sensitive information.
</p>
</section>
<!-- =================================================================== -->
<section>
+ <marker id="Complex State" />
<title>Complex State</title>
<p>
The callback mode
<seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>
enables using a non-atom state as described in section
- <seealso marker="#callback_modes">Callback Modes</seealso>,
+ <seealso marker="#Callback Modes">Callback Modes</seealso>,
for example, a complex state term like a tuple.
</p>
<p>
- One reason to use this is when you have
- a state item that affects the event handling,
- in particular in combination with postponing events.
- We complicate the previous example
+ One reason to use this is when you have a state item
+ that when changed should cancel the
+ <seealso marker="#State Time-Outs">state time-out</seealso>,
+ or one that affects the event handling
+ in combination with postponing events.
+ We will complicate the previous example
by introducing a configurable lock button
(this is the state item in question),
which in the <c>open</c> state immediately locks the door,
@@ -1273,11 +1633,10 @@ format_status(Opt, [_PDict,State,Data]) ->
-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]).
--export([init/1,terminate/3,code_change/4,format_status/2]).
+-export([init/1,callback_mode/0,terminate/3,code_change/4,format_status/2]).
-export([handle_event/4]).
start_link(Code, LockButton) ->
@@ -1295,68 +1654,68 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
- Data = #{code => Code, remaining => undefined, timer => undefined},
- enter(?CALLBACK_MODE, {locked,LockButton}, Data, []).
+ Data = #{code => Code, remaining => undefined},
+ {ok, {locked,LockButton}, Data}.
+
+callback_mode() ->
+ [handle_event_function,state_enter].
handle_event(
{call,From}, {set_lock_button,NewLockButton},
{StateName,OldLockButton}, Data) ->
- {next_state,{StateName,NewLockButton},Data,
+ {next_state, {StateName,NewLockButton}, Data,
[{reply,From,OldLockButton}]};
handle_event(
{call,From}, code_length,
{_StateName,_LockButton}, #{code := Code}) ->
{keep_state_and_data,
[{reply,From,length(Code)}]};
+%%
+%% State: locked
handle_event(
EventType, EventContent,
{locked,LockButton}, #{code := Code, remaining := Remaining} = Data) ->
- case {EventType,EventContent} of
- {internal,enter} ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_lock(),
- {keep_state,Data#{remaining := Code}};
- {{call,From},{button,Digit}} ->
+ {keep_state, Data#{remaining := Code}};
+ {timeout, _} ->
+ {keep_state, Data#{remaining := Code}};
+ {{call,From}, {button,Digit}} ->
case Remaining of
[Digit] -> % Complete
- next_state(
- {open,LockButton}, Data,
- [{reply,From,ok}]);
+ {next_state, {open,LockButton}, Data,
+ [{reply,From,ok}]};
[Digit|Rest] -> % Incomplete
- {keep_state,Data#{remaining := Rest},
+ {keep_state, Data#{remaining := Rest, 30000},
[{reply,From,ok}]};
[_|_] -> % Wrong
- {keep_state,Data#{remaining := Code},
+ {keep_state, Data#{remaining := Code},
[{reply,From,ok}]}
end
end;
+%%
+%% State: open
handle_event(
EventType, EventContent,
- {open,LockButton}, #{timer := Timer} = Data) ->
- case {EventType,EventContent} of
- {internal,enter} ->
- Tref = erlang:start_timer(10000, self(), lock),
+ {open,LockButton}, Data) ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
do_unlock(),
- {keep_state,Data#{timer := Tref}};
- {info,{timeout,Timer,lock}} ->
- next_state({locked,LockButton}, Data, []);
- {{call,From},{button,Digit}} ->
+ {keep_state_and_data, [{state_timeout,10000,lock}]};
+ {state_timeout, 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}]);
+ {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]}.
-
do_lock() ->
io:format("Locked~n", []).
do_unlock() ->
@@ -1366,7 +1725,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {?CALLBACK_MODE,State,Data}.
+ {ok,State,Data}.
format_status(Opt, [_PDict,State,Data]) ->
StateData =
{State,
@@ -1393,6 +1752,7 @@ format_status(Opt, [_PDict,State,Data]) ->
<!-- =================================================================== -->
<section>
+ <marker id="Hibernation" />
<title>Hibernation</title>
<p>
If you have many servers in one node
@@ -1418,20 +1778,21 @@ format_status(Opt, [_PDict,State,Data]) ->
</p>
<code type="erl"><![CDATA[
...
+%% State: open
handle_event(
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},[hibernate]};
+ {open,LockButton}, Data) ->
+ case {EventType, EventContent} of
+ {enter, _OldState} ->
+ do_unlock(),
+ {keep_state_and_data,
+ [{state_timeout,10000,lock},hibernate]};
...
]]></code>
<p>
- The
- <seealso marker="stdlib:gen_statem#type-hibernate"><c>[hibernate]</c></seealso>
- action list on the last line
+ The atom
+ <seealso marker="stdlib:gen_statem#type-hibernate"><c>hibernate</c></seealso>
+ in the action list on the last line
when entering the <c>{open,_}</c> state is the only change.
If any event arrives in the <c>{open,_},</c> state, we
do not bother to rehibernate, so the server stays
@@ -1446,6 +1807,10 @@ handle_event(
<c>{open,_}</c> state, which would clutter the code.
</p>
<p>
+ Another not uncommon scenario is to use the event time-out
+ to triger hibernation after a certain time of inactivity.
+ </p>
+ <p>
This server probably does not use
heap memory worth hibernating for.
To gain anything from hibernation, your server would
diff --git a/system/doc/design_principles/sup_princ.xml b/system/doc/design_principles/sup_princ.xml
index 0a24e97950..06ca44a9f6 100644
--- a/system/doc/design_principles/sup_princ.xml
+++ b/system/doc/design_principles/sup_princ.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2016</year>
+ <year>1997</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -163,7 +163,9 @@ SupFlags = #{strategy => Strategy, ...}</code>
SupFlags = #{intensity => MaxR, period => MaxT, ...}</code>
<p>If more than <c>MaxR</c> number of restarts occur in the last
<c>MaxT</c> seconds, the supervisor terminates all the child
- processes and then itself.</p>
+ processes and then itself.
+ The termination reason for the supervisor itself in that case will be
+ <c>shutdown</c>.</p>
<p>When the supervisor terminates, then the next higher-level
supervisor takes some action. It either restarts the terminated
supervisor or terminates itself.</p>
@@ -173,6 +175,69 @@ SupFlags = #{intensity => MaxR, period => MaxT, ...}</code>
<p>The keys <c>intensity</c> and <c>period</c> are optional in the
supervisor flags map. If they are not given, they default
to <c>1</c> and <c>5</c>, respectively.</p>
+ <section>
+ <title>Tuning the intensity and period</title>
+ <p>The default values are 1 restart per 5 seconds. This was chosen to
+ be safe for most systems, even with deep supervision hierarchies,
+ but you will probably want to tune the settings for your particular
+ use case.</p>
+ <p>First, the intensity decides how big bursts of restarts you want
+ to tolerate. For example, you might want to accept a burst of at
+ most 5 or 10 attempts, even within the same second, if it results
+ in a successful restart.</p>
+ <p>Second, you need to consider the sustained failure rate, if
+ crashes keep happening but not often enough to make the supervisor
+ give up. If you set intensity to 10 and set the period as low as 1,
+ the supervisor will allow child processes to keep restarting up to
+ 10 times per second, forever, filling your logs with crash reports
+ until someone intervenes manually.</p>
+ <p>You should therefore set the period to be long enough that you can
+ accept that the supervisor keeps going at that rate. For example,
+ if you have picked an intensity value of 5, then setting the period
+ to 30 seconds will give you at most one restart per 6 seconds for
+ any longer period of time, which means that your logs won't fill up
+ too quickly, and you will have a chance to observe the failures and
+ apply a fix.</p>
+ <p>These choices depend a lot on your problem domain. If you don't
+ have real time monitoring and ability to fix problems quickly, for
+ example in an embedded system, you might want to accept at most
+ one restart per minute before the supervisor should give up and
+ escalate to the next level to try to clear the error automatically.
+ On the other hand, if it is more important that you keep trying
+ even at a high failure rate, you might want a sustained rate of as
+ much as 1-2 restarts per second.</p>
+ <p>Avoiding common mistakes:</p>
+ <list type="bulleted">
+ <item>
+ <p>Do not forget to consider the burst rate. If you set intensity
+ to 1 and period to 6, it gives the same sustained error rate as
+ 5/30 or 10/60, but will not allow even 2 restart attempts in
+ quick succession. This is probably not what you wanted.</p>
+ </item>
+ <item>
+ <p>Do not set the period to a very high value if you want to
+ tolerate bursts. If you set intensity to 5 and period to 3600
+ (one hour), the supervisor will allow a short burst of 5
+ restarts, but then gives up if it sees another single restart
+ almost an hour later. You probably want to regard those crashes
+ as separate incidents, so setting the period to 5 or 10 minutes
+ will be more reasonable.</p>
+ </item>
+ <item>
+ <p>If your application has multiple levels of supervision, then
+ do not simply set the restart intensities to the same values on
+ all levels. Keep in mind that the total number of restarts
+ (before the top level supervisor gives up and terminates the
+ application) will be the product of the intensity values of all
+ the supervisors above the failing child process.</p>
+ <p>For example, if the top level allows 10 restarts, and the next
+ level also allows 10, a crashing child below that level will be
+ restarted 100 times, which is probably excessive. Allowing at
+ most 3 restarts for the top level supervisor might be a better
+ choice in this case.</p>
+ </item>
+ </list>
+ </section>
</section>
<section>
@@ -211,7 +276,6 @@ child_spec() = #{id => child_id(), % mandatory
<list type="bulleted">
<item><c>supervisor:start_link</c></item>
<item><c>gen_server:start_link</c></item>
- <item><c>gen_fsm:start_link</c></item>
<item><c>gen_statem:start_link</c></item>
<item><c>gen_event:start_link</c></item>
<item>A function compliant with these functions. For details,
@@ -276,7 +340,7 @@ child_spec() = #{id => child_id(), % mandatory
<p><c>modules</c> are to be a list with one element
<c>[Module]</c>, where <c>Module</c> is the name of
the callback module, if the child process is a supervisor,
- gen_server, gen_fsm or gen_statem.
+ gen_server, gen_statem.
If the child process is a gen_event,
the value shall be <c>dynamic</c>.</p>
<p>This information is used by the release handler during
diff --git a/system/doc/design_principles/xmlfiles.mk b/system/doc/design_principles/xmlfiles.mk
index e476255d62..fbcaf9c7d9 100644
--- a/system/doc/design_principles/xmlfiles.mk
+++ b/system/doc/design_principles/xmlfiles.mk
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2009-2016. All Rights Reserved.
+# Copyright Ericsson AB 2009-2017. 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.
@@ -24,7 +24,6 @@ DESIGN_PRINCIPLES_CHAPTER_FILES = \
des_princ.xml \
distributed_applications.xml \
events.xml \
- fsm.xml \
statem.xml \
gen_server_concepts.xml \
included_applications.xml \