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/Makefile17
-rw-r--r--system/doc/design_principles/applications.xml2
-rw-r--r--system/doc/design_principles/appup_cookbook.xml5
-rw-r--r--system/doc/design_principles/book.xml2
-rw-r--r--system/doc/design_principles/code_lock.diabin0 -> 2932 bytes
-rw-r--r--system/doc/design_principles/code_lock.pngbin0 -> 59160 bytes
-rw-r--r--system/doc/design_principles/code_lock_2.diabin0 -> 2621 bytes
-rw-r--r--system/doc/design_principles/code_lock_2.pngbin0 -> 48927 bytes
-rw-r--r--system/doc/design_principles/des_princ.xml6
-rw-r--r--system/doc/design_principles/distributed_applications.xml2
-rw-r--r--system/doc/design_principles/events.xml2
-rw-r--r--system/doc/design_principles/fsm.xml12
-rw-r--r--system/doc/design_principles/gen_server_concepts.xml2
-rw-r--r--system/doc/design_principles/included_applications.xml2
-rw-r--r--system/doc/design_principles/part.xml3
-rw-r--r--system/doc/design_principles/release_handling.xml4
-rw-r--r--system/doc/design_principles/release_structure.xml2
-rw-r--r--system/doc/design_principles/spec_proc.xml33
-rw-r--r--system/doc/design_principles/statem.xml1457
-rw-r--r--system/doc/design_principles/sup_princ.xml15
-rw-r--r--system/doc/design_principles/xmlfiles.mk3
21 files changed, 1539 insertions, 30 deletions
diff --git a/system/doc/design_principles/Makefile b/system/doc/design_principles/Makefile
index 29df484279..937b3e28c8 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.
@@ -58,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)
@@ -85,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:
@@ -109,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/applications.xml b/system/doc/design_principles/applications.xml
index 4d73e0fc66..0a1b65ea8e 100644
--- a/system/doc/design_principles/applications.xml
+++ b/system/doc/design_principles/applications.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2014</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
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 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2014</year>
+ <year>2003</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -50,7 +50,8 @@
<p>In a system implemented according to the OTP design principles,
all processes, except system processes and special processes,
reside in one of the behaviours <c>supervisor</c>,
- <c>gen_server</c>, <c>gen_fsm</c>, or <c>gen_event</c>. These
+ <c>gen_server</c>, <c>gen_fsm</c>,
+ <c>gen_statem</c> or <c>gen_event</c>. These
belong to the STDLIB application and upgrading/downgrading
normally requires an emulator restart.</p>
<p>OTP thus provides no support for changing residence modules except
diff --git a/system/doc/design_principles/book.xml b/system/doc/design_principles/book.xml
index 663551df2f..a734f1d1e4 100644
--- a/system/doc/design_principles/book.xml
+++ b/system/doc/design_principles/book.xml
@@ -4,7 +4,7 @@
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<header titlestyle="normal">
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/system/doc/design_principles/code_lock.dia b/system/doc/design_principles/code_lock.dia
new file mode 100644
index 0000000000..8e6ff8a898
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..745fd91920
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..142909a2f5
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..ecf7b0d799
--- /dev/null
+++ b/system/doc/design_principles/code_lock_2.png
Binary files differ
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 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -226,7 +226,9 @@ free(Ch, {Alloc, Free} = Channels) ->
<item><p><seealso marker="gen_server_concepts">gen_server</seealso></p>
<p>For implementing the server of a client-server relation</p></item>
<item><p><seealso marker="fsm">gen_fsm</seealso></p>
- <p>For implementing finite-state machines</p></item>
+ <p>For implementing finite-state machines (Old)</p></item>
+ <item><p><seealso marker="statem">gen_statem</seealso></p>
+ <p>For implementing state machines (New)</p></item>
<item><p><seealso marker="events">gen_event</seealso></p>
<p>For implementing event handling functionality</p></item>
<item><p><seealso marker="sup_princ">supervisor</seealso></p>
diff --git a/system/doc/design_principles/distributed_applications.xml b/system/doc/design_principles/distributed_applications.xml
index f1624a9ad7..a1a0149eb5 100644
--- a/system/doc/design_principles/distributed_applications.xml
+++ b/system/doc/design_principles/distributed_applications.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2013</year>
+ <year>2003</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/system/doc/design_principles/events.xml b/system/doc/design_principles/events.xml
index 3c9d0996dc..e37b8b460d 100644
--- a/system/doc/design_principles/events.xml
+++ b/system/doc/design_principles/events.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/system/doc/design_principles/fsm.xml b/system/doc/design_principles/fsm.xml
index f58b50cbff..4f2b75e6e8 100644
--- a/system/doc/design_principles/fsm.xml
+++ b/system/doc/design_principles/fsm.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -30,6 +30,16 @@
<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>
diff --git a/system/doc/design_principles/gen_server_concepts.xml b/system/doc/design_principles/gen_server_concepts.xml
index f8f98918fa..c1b98189d5 100644
--- a/system/doc/design_principles/gen_server_concepts.xml
+++ b/system/doc/design_principles/gen_server_concepts.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/system/doc/design_principles/included_applications.xml b/system/doc/design_principles/included_applications.xml
index 3257795e5f..34501c0296 100644
--- a/system/doc/design_principles/included_applications.xml
+++ b/system/doc/design_principles/included_applications.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2013</year>
+ <year>2003</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
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 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1997</year><year>2013</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -31,6 +31,7 @@
<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"/>
<xi:include href="spec_proc.xml"/>
diff --git a/system/doc/design_principles/release_handling.xml b/system/doc/design_principles/release_handling.xml
index 20ddc3dbf5..4f71ad4437 100644
--- a/system/doc/design_principles/release_handling.xml
+++ b/system/doc/design_principles/release_handling.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2014</year>
+ <year>2003</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -249,7 +249,7 @@
<p>If <c>Modules=dynamic</c>, which is the case for event
managers, the event manager process informs the release handler
about the list of currently installed event handlers
- (<c>gen_fsm</c>), and it is checked if the module name is in
+ (<c>gen_event</c>), and it is checked if the module name is in
this list instead.</p>
<p>The release handler suspends, asks for code change, and
resumes processes by calling the functions
diff --git a/system/doc/design_principles/release_structure.xml b/system/doc/design_principles/release_structure.xml
index 6e7ab06094..a0ab21c43f 100644
--- a/system/doc/design_principles/release_structure.xml
+++ b/system/doc/design_principles/release_structure.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2013</year>
+ <year>2003</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/system/doc/design_principles/spec_proc.xml b/system/doc/design_principles/spec_proc.xml
index 3d7a88da3f..5b156ac263 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>2014</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -522,9 +522,36 @@ init(Parent, Name, Module) ->
-module(db).
-behaviour(simple_server).
--export([init/0, handle_req/2, terminate/0]).
+-export([init/1, handle_req/2, terminate/0]).
...</code>
+
+ <p>The contracts specified with <c>-callback</c> attributes in
+ behaviour modules can be further refined by adding <c>-spec</c>
+ attributes in callback modules. This can be useful as
+ <c>-callback</c> contracts are usually generic. The same callback
+ module with contracts for the callbacks:</p>
+
+ <code type="none">
+-module(db).
+-behaviour(simple_server).
+
+-export([init/1, handle_req/2, terminate/0]).
+
+-record(state, {field1 :: [atom()], field2 :: integer()}).
+
+-type state() :: #state{}.
+-type request() :: {'store', term(), term()};
+ {'lookup', term()}.
+
+...
+
+-spec handle_req(request(), state()) -> {'ok', term()}.
+
+...</code>
+
+ <p>Each <c>-spec</c> contract is to be a subtype of the respective
+ <c>-callback</c> contract.</p>
+
</section>
</chapter>
-
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
new file mode 100644
index 0000000000..aea623851a
--- /dev/null
+++ b/system/doc/design_principles/statem.xml
@@ -0,0 +1,1457 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <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_statem Behavior</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>statem.xml</file>
+ </header>
+ <marker id="gen_statem behaviour"></marker>
+ <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
+ functions are described in detail.
+ </p>
+ <note>
+ <p>
+ This is a new behavior in Erlang/OTP 19.0.
+ It has been thoroughly reviewed, is stable enough
+ to be used by at least two heavy OTP applications, and is here to stay.
+ Depending on user feedback, we do not expect
+ but can find it necessary to make minor
+ not backward compatible changes into Erlang/OTP 20.0.
+ </p>
+ </note>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Event-Driven State Machines</title>
+ <p>
+ 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
+ some kind of values.
+ </p>
+ <p>
+ 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 following form:
+ </p>
+ <pre>
+State(S) x Event(E) -> Actions(A), State(S')</pre>
+ <p>These relations are interpreted as follows:
+ if we are in state <c>S</c> and event <c>E</c> occurs, we
+ are to perform actions <c>A</c> and make a transition to
+ state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c>.
+ </p>
+ <p>
+ As <c>A</c> and <c>S'</c> depend only on
+ <c>S</c> and <c>E</c>, the kind of state machine described
+ here is a Mealy Machine
+ (see, for example, the corresponding Wikipedia article).
+ </p>
+ <p>
+ Like most <c>gen_</c> behaviors, <c>gen_statem</c> keeps
+ a server <c>Data</c> besides the state. Because of this, and as
+ there is no restriction on the number of states
+ (assuming that there is enough virtual machine memory)
+ or on the number of distinct input events,
+ a state machine implemented with this behavior
+ is in fact Turing complete.
+ But it feels mostly like an Event-Driven Mealy Machine.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <marker id="callback_modes" />
+ <title>Callback Modes</title>
+ <p>
+ The <c>gen_statem</c> behavior supports two callback modes:
+ </p>
+ <list type="bulleted">
+ <item>
+ <p>
+ In mode
+ <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>,
+ the state transition rules are written as some Erlang
+ functions, which conform to the following convention:
+ </p>
+ <pre>
+StateName(EventType, EventContent, Data) ->
+ .. code for actions here ...
+ {next_state, NewStateName, NewData}.</pre>
+ </item>
+ <item>
+ <p>
+ In mode
+ <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>,
+ only one Erlang function provides all state transition rules:
+ </p>
+ <pre>
+handle_event(EventType, EventContent, State, Data) ->
+ .. code for actions here ...
+ {next_state, NewState, NewData}</pre>
+ </item>
+ </list>
+ <p>
+ Both these modes allow other return tuples; see
+ <seealso marker="stdlib:gen_statem#Module:StateName/3"><c>Module:StateName/3</c></seealso>
+ in the <c>gen_statem</c> manual page.
+ These other return tuples can, for example, stop the machine,
+ execute state transition actions on the machine engine itself,
+ and send replies.
+ </p>
+
+ <section>
+ <title>Choosing the Callback Mode</title>
+ <p>
+ The two
+ <seealso marker="#callback_modes">callback modes</seealso>
+ give different possibilities
+ and restrictions, but one goal remains:
+ you want to handle all possible combinations of
+ events and states.
+ </p>
+ <p>
+ This can be done, for example, by focusing on one state at the time
+ and for every state ensure that all events are handled.
+ Alternatively, you can focus on one event at the time
+ and ensure that it is handled in every state.
+ You can also use a mix of these strategies.
+ </p>
+ <p>
+ With <c>state_functions</c>, you are restricted to use
+ atom-only states, and the <c>gen_statem</c> engine
+ branches depending 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 to focus on one state at the time.
+ </p>
+ <p>
+ This mode fits well when you have a regular state diagram,
+ like the ones in this chapter, which describes all events and actions
+ belonging to a state visually around that state,
+ and each state has its unique name.
+ </p>
+ <p>
+ With <c>handle_event_function</c>, you are free to mix strategies,
+ as all events and states are handled in the same callback function.
+ </p>
+ <p>
+ This mode works equally well when you want to focus on
+ one event at the time or on
+ one state at the time, but function
+ <seealso marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>
+ quickly grows too large to handle without branching to
+ helper functions.
+ </p>
+ <p>
+ The mode enables the use of non-atom states, for example,
+ complex states or even hierarchical states.
+ If, for example, a state diagram is largely alike
+ for the client side and the server side of a protocol,
+ you can have a state <c>{StateName,server}</c> or
+ <c>{StateName,client}</c>,
+ and make <c>StateName</c> determine 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.
+ </p>
+ </section>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Example</title>
+ <p>
+ This example starts off as equivalent to the example in section
+ <seealso marker="fsm"><c>gen_fsm</c> Behavior</seealso>.
+ In later sections, additions and tweaks are made
+ using features in <c>gen_statem</c> that <c>gen_fsm</c> does not have.
+ The end of this chapter provides the example again
+ with all the added features.
+ </p>
+ <p>
+ A door with a code lock can be seen as a state machine.
+ Initially, the door is locked. When someone presses a button,
+ an event is generated.
+ Depending on what buttons have been pressed before,
+ the sequence so far can be correct, incomplete, or wrong.
+ If correct, the door is unlocked for 10 seconds (10,000 milliseconds).
+ If incomplete, we wait for another button to be pressed. If
+ wrong, we start all over, waiting for a new button sequence.
+ </p>
+ <image file="../design_principles/code_lock.png">
+ <icaption>Code Lock State Diagram</icaption>
+ </image>
+ <p>
+ 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([locked/3,open/3]).
+
+start_link(Code) ->
+ gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
+
+button(Digit) ->
+ gen_statem:cast(?NAME, {button,Digit}).
+
+init(Code) ->
+ do_lock(),
+ Data = #{code => Code, remaining => Code},
+ {?CALLBACK_MODE,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) ->
+ {?CALLBACK_MODE,State,Data}.
+ ]]></code>
+ <p>The code is explained in the next sections.</p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Starting gen_statem</title>
+ <p>
+ In the example in the previous section, <c>gen_statem</c> is
+ started by calling <c>code_lock:start_link(Code)</c>:
+ </p>
+ <code type="erl"><![CDATA[
+start_link(Code) ->
+ gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
+ ]]></code>
+ <p>
+ <c>start_link</c> calls function
+ <seealso marker="stdlib:gen_statem#start_link/4"><c>gen_statem:start_link/4</c></seealso>,
+ which spawns and links to a new process, a <c>gen_statem</c>.
+ </p>
+ <list type="bulleted">
+ <item>
+ <p>
+ The first argument, <c>{local,?NAME}</c>, specifies
+ the name. In this case, the <c>gen_statem</c> is locally
+ registered as <c>code_lock</c> through the macro <c>?NAME</c>.
+ </p>
+ <p>
+ If the name is omitted, the <c>gen_statem</c> is not registered.
+ Instead its pid must be used. The name can also be specified
+ as <c>{global,Name}</c>, then the <c>gen_statem</c> is
+ registered using
+ <seealso marker="kernel:global#register_name/2"><c>global:register_name/2</c></seealso>
+ in <c>Kernel</c>.
+ </p>
+ </item>
+ <item>
+ <p>
+ The second argument, <c>?MODULE</c>, is the name of
+ the callback module, that is, the module where the callback
+ functions are located, which is this module.
+ </p>
+ <p>
+ The interface functions (<c>start_link/1</c> and <c>button/1</c>)
+ are located in the same module as the callback functions
+ (<c>init/1</c>, <c>locked/3</c>, and <c>open/3</c>).
+ It is normally good programming practice to have the client-side
+ code and the server-side code contained in one module.
+ </p>
+ </item>
+ <item>
+ <p>
+ The third argument, <c>Code</c>, is a list of digits, which
+ is the correct unlock code that is passed
+ to callback function <c>init/1</c>.
+ </p>
+ </item>
+ <item>
+ <p>
+ The fourth argument, <c>[]</c>, is a list of options.
+ For the available options, see
+ <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link/3</c></seealso>.
+ </p>
+ </item>
+ </list>
+ <p>
+ If name registration succeeds, the new <c>gen_statem</c> process
+ calls 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>,
+ in this case <c>locked</c>; assuming that the door is locked to begin
+ with. <c>Data</c> is the internal server data of the <c>gen_statem</c>.
+ Here the server data is a <seealso marker="stdlib:maps">map</seealso>
+ with key <c>code</c> that stores
+ the correct button sequence, and key <c>remaining</c>
+ that stores the remaining correct button sequence
+ (the same as the <c>code</c> to begin with).
+ </p>
+ <code type="erl"><![CDATA[
+init(Code) ->
+ do_lock(),
+ Data = #{code => Code, remaining => Code},
+ {?CALLBACK_MODE,locked,Data}.
+ ]]></code>
+ <p>Function
+ <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso>
+ is synchronous. It does not return until the <c>gen_statem</c>
+ is initialized and is ready to receive events.
+ </p>
+ <p>
+ Function
+ <seealso marker="stdlib:gen_statem#start_link/3"><c>gen_statem:start_link</c></seealso>
+ must be used if the <c>gen_statem</c>
+ is part of a supervision tree, that is, started by a supervisor.
+ Another function,
+ <seealso marker="stdlib:gen_statem#start/3"><c>gen_statem:start</c></seealso>
+ can be used to start a standalone <c>gen_statem</c>, that is,
+ a <c>gen_statem</c> that is not part of a supervision tree.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Handling Events</title>
+ <p>The function notifying the code lock about a button event is
+ implemented using
+ <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast/2</c></seealso>:
+ </p>
+ <code type="erl"><![CDATA[
+button(Digit) ->
+ gen_statem:cast(?NAME, {button,Digit}).
+ ]]></code>
+ <p>
+ The first argument is the name of the <c>gen_statem</c> and must
+ agree with the name used to start it. So, we use the
+ same macro <c>?NAME</c> as when starting.
+ <c>{button,Digit}</c> is the event content.
+ </p>
+ <p>
+ The event is made into a message and sent to the <c>gen_statem</c>.
+ 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>.
+ <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>.
+ </p>
+ <code type="erl"><![CDATA[
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ 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}.
+ ]]></code>
+ <p>
+ If the door is locked and a button is pressed, the pressed
+ button is compared with the next correct button.
+ Depending on the result, the door is either unlocked
+ and the <c>gen_statem</c> goes to state <c>open</c>,
+ or the door remains in state <c>locked</c>.
+ </p>
+ <p>
+ If the pressed button is incorrect, the server data
+ 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.
+ </p>
+ </section>
+
+ <section>
+ <title>Event 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};
+ ]]></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.
+ 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) ->
+ do_lock(),
+ {next_state,locked,Data};
+ ]]></code>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>All State Events</title>
+ <p>
+ Sometimes events can arrive in any state of the <c>gen_statem</c>.
+ It is convenient to handle these in a common state handler function
+ that all state functions call for events not specific to the state.
+ </p>
+ <p>
+ Consider a <c>code_length/0</c> function that returns
+ the length of the correct code
+ (that should not be sensitive to reveal).
+ We dispatch all events that are not state-specific
+ to the common function <c>handle_event/3</c>:
+ </p>
+ <code type="erl"><![CDATA[
+...
+-export([button/1,code_length/0]).
+...
+
+code_length() ->
+ gen_statem:call(?NAME, 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)}]}.
+ ]]></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.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>One Event Handler</title>
+ <p>
+ If mode <c>handle_event_function</c> is used,
+ all events are handled in
+ <seealso marker="stdlib:gen_statem#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>
+ and we can (but do not have to) use an event-centered approach
+ where we first branch depending on event
+ and then depending on state:
+ </p>
+ <code type="erl"><![CDATA[
+...
+-define(CALLBACK_MODE, handle_event_function).
+
+...
+-export([handle_event/4]).
+
+...
+
+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}.
+
+...
+ ]]></code>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Stopping</title>
+
+ <section>
+ <title>In a Supervision Tree</title>
+ <p>
+ If the <c>gen_statem</c> is part of a supervision tree,
+ no stop function is needed.
+ The <c>gen_statem</c> is automatically terminated by its supervisor.
+ Exactly how this is done is defined by a
+ <seealso marker="sup_princ#shutdown">shutdown strategy</seealso>
+ set in the supervisor.
+ </p>
+ <p>
+ If it is necessary to clean up before termination, the shutdown
+ strategy must be a time-out value and the <c>gen_statem</c> must
+ in function <c>init/1</c> set itself to trap exit signals
+ by calling
+ <seealso marker="erts:erlang#process_flag/2"><c>process_flag(trap_exit, true)</c></seealso>:
+ </p>
+ <code type="erl"><![CDATA[
+init(Args) ->
+ process_flag(trap_exit, true),
+ do_lock(),
+ ...
+ ]]></code>
+ <p>
+ When ordered to shut down, the <c>gen_statem</c> then calls
+ callback function <c>terminate(shutdown, State, Data)</c>.
+ </p>
+ <p>
+ In the following example, function <c>terminate/3</c>
+ locks the door if it is open, so we do not accidentally leave the door
+ open when the supervision tree terminates:
+ </p>
+ <code type="erl"><![CDATA[
+terminate(_Reason, State, _Data) ->
+ State =/= locked andalso do_lock(),
+ ok.
+ ]]></code>
+ </section>
+
+ <section>
+ <title>Standalone gen_statem</title>
+ <p>
+ If the <c>gen_statem</c> is not part of a supervision tree,
+ it can be stopped using
+ <seealso marker="stdlib:gen_statem#stop/1"><c>gen_statem:stop</c></seealso>,
+ preferably through an API function:
+ </p>
+ <code type="erl"><![CDATA[
+...
+-export([start_link/1,stop/0]).
+
+...
+stop() ->
+ gen_statem:stop(?NAME).
+ ]]></code>
+ <p>
+ This makes the <c>gen_statem</c> call callback function
+ <c>terminate/3</c> just like for a supervised server
+ and waits for the process to terminate.
+ </p>
+ </section>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Actions</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.
+ </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:
+ </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.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Event Types</title>
+ <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.
+ </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>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>
+ <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.
+ </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>.
+ </p>
+ <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.
+ </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 wait for it to expire while ignoring button events:
+ </p>
+ <code type="erl"><![CDATA[
+...
+locked(
+ cast, {button,Digit},
+ #{code := Code, remaining := Remaining} = Data) ->
+ 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};
+...
+ ]]></code>
+ <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,
+ so ensure that you do not accidentally postpone such messages.
+ </p>
+ <p>
+ Another way to cancel a timer is not to cancel it,
+ but to ignore it if it arrives in a state
+ where it is known to be late.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Postponing Events</title>
+ <p>
+ 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, that is, <c>OldState =/= NewState</c>.
+ </p>
+ <p>
+ Postponing is ordered by the state transition
+ <seealso marker="stdlib:gen_statem#type-action">action</seealso>
+ <c>postpone</c>.
+ </p>
+ <p>
+ In this example, instead of ignoring button events
+ while in the <c>open</c> state, we can postpone them
+ and they are queued and later handled in the <c>locked</c> state:
+ </p>
+ <code type="erl"><![CDATA[
+...
+open(cast, {button,_}, Data) ->
+ {keep_state,Data,[postpone]};
+...
+ ]]></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.
+ </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>.
+ </p>
+
+ <section>
+ <title>Fuzzy State Diagrams</title>
+ <p>
+ 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.
+ </p>
+ <p>
+ Possible actions: ignore as in drop the event
+ (maybe log it) or deal with the event in some other state
+ as in postpone it.
+ </p>
+ </section>
+
+ <section>
+ <title>Selective Receive</title>
+ <p>
+ Erlang's selective receive statement is often used to
+ describe simple state machine examples in straightforward
+ Erlang code. The following is a possible implementation of
+ the first example:
+ </p>
+ <code type="erl"><![CDATA[
+-module(code_lock).
+-define(NAME, code_lock_1).
+-export([start_link/1,button/1]).
+
+start_link(Code) ->
+ spawn(
+ fun () ->
+ true = register(?NAME, self()),
+ do_lock(),
+ locked(Code, Code)
+ end).
+
+button(Digit) ->
+ ?NAME ! {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", []).
+ ]]></code>
+ <p>
+ The selective receive in this case causes implicitly <c>open</c>
+ to postpone any events to the <c>locked</c> state.
+ </p>
+ <p>
+ A selective receive cannot be used from a <c>gen_statem</c>
+ behavior as for any <c>gen_*</c> behavior,
+ as the receive statement is within the <c>gen_*</c> engine itself.
+ It must be there because all
+ <seealso marker="stdlib:sys"><c>sys</c></seealso>
+ compatible behaviors must respond to system messages and therefore
+ do that in their engine receive loop,
+ passing non-system messages to the callback module.
+ </p>
+ <p>
+ The state transition
+ <seealso marker="stdlib:gen_statem#type-action">action</seealso>
+ <c>postpone</c> is designed to model
+ selective receives. A selective receive implicitly postpones
+ any not received events, but the <c>postpone</c>
+ state transition action explicitly postpones one received event.
+ </p>
+ <p>
+ Both mechanisms have the same theoretical
+ time and memory complexity, while the selective receive
+ language construct has smaller constant factors.
+ </p>
+ </section>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Self-Generated Events</title>
+ <p>
+ It can sometimes be beneficial to be able to generate events
+ to your own state machine.
+ This can be done with the state transition
+ <seealso marker="stdlib:gen_statem#type-action">action</seealso>
+ <c>{next_event,EventType,EventContent}</c>.
+ </p>
+ <p>
+ You can generate events of any existing
+ <seealso marker="stdlib:gen_statem#type-action">type</seealso>,
+ but the <c>internal</c> type can only be generated through action
+ <c>next_event</c>. Hence, it cannot come from an external source,
+ so you can be certain that an <c>internal</c> event is an event
+ from your state machine to itself.
+ </p>
+ <p>
+ One example of using self-generated events 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.
+ </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:
+ </p>
+ <code type="erl"><![CDATA[
+...
+-define(CALLBACK_MODE, state_functions).
+
+...
+
+init(Code) ->
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ enter(?CALLBACK_MODE, 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}]}.
+ ]]></code>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Example Revisited</title>
+ <p>
+ This section includes the example after all mentioned modifications
+ and some more using the entry actions,
+ which deserves a new state diagram:
+ </p>
+ <image file="../design_principles/code_lock_2.png">
+ <icaption>Code Lock State Diagram Revisited</icaption>
+ </image>
+ <p>
+ Notice that this state diagram does not specify how to handle
+ a button event in the state <c>open</c>. So, you need to
+ read somewhere else that unspecified events
+ must be ignored as in not consumed but handled in some other state.
+ Also, the state diagram does not show that the <c>code_length/0</c>
+ call must be handled in every state.
+ </p>
+
+ <section>
+ <title>Callback Mode: state_functions</title>
+ <p>
+ Using state functions:
+ </p>
+ <code type="erl"><![CDATA[
+-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([locked/3,open/3]).
+
+start_link(Code) ->
+ gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
+stop() ->
+ gen_statem:stop(?NAME).
+
+button(Digit) ->
+ gen_statem:cast(?NAME, {button,Digit}).
+code_length() ->
+ gen_statem:call(?NAME, code_length).
+
+init(Code) ->
+ process_flag(trap_exit, true),
+ Data = #{code => Code},
+ enter(?CALLBACK_MODE, 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) ->
+ {?CALLBACK_MODE,State,Data}.
+ ]]></code>
+ </section>
+
+ <section>
+ <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:
+ </p>
+ <code type="erl"><![CDATA[
+...
+-define(CALLBACK_MODE, handle_event_function).
+
+...
+-export([handle_event/4]).
+
+...
+
+%% 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)}]}.
+
+...
+ ]]></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
+ for a code lock, but it at least illustrates event postponing.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Filter the State</title>
+ <p>
+ The example servers so far in this chapter
+ print the full internal state in the error log, for example,
+ when killed by an exit signal or because of an internal error.
+ This state contains both the code lock code
+ and which digits that remain to unlock.
+ </p>
+ <p>
+ This state data can be regarded as sensitive,
+ and maybe not what you want in the error log
+ because of some unpredictable event.
+ </p>
+ <p>
+ Another reason to filter the state can be
+ that the state is too large to print, as it fills
+ the error log with uninteresting details.
+ </p>
+ <p>
+ To avoid this, you can format the internal state
+ that gets in the error log and gets returned from
+ <seealso marker="stdlib:sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
+ by implementing function
+ <seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso>,
+ for example like this:
+ </p>
+ <code type="erl"><![CDATA[
+...
+-export([init/1,terminate/3,code_change/4,format_status/2]).
+...
+
+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.
+ ]]></code>
+ <p>
+ It is not mandatory to implement a
+ <seealso marker="stdlib:gen_statem#Module:format_status/2"><c>Module:format_status/2</c></seealso>
+ function. If you do not, a default implementation is used that
+ does the same as this example function without filtering
+ the <c>Data</c> term, that is, <c>StateData = {State,Data}</c>.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <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>,
+ 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
+ by introducing a configurable lock button
+ (this is the state item in question),
+ which in the <c>open</c> state immediately locks the door,
+ and an API function <c>set_lock_button/1</c> to set the lock button.
+ </p>
+ <p>
+ Suppose now that we call <c>set_lock_button</c>
+ while the door is open,
+ and have already postponed a button event
+ that until now was not the lock button.
+ The sensible thing can be to say that
+ the button was pressed too early so it is
+ not to be recognized as the lock button.
+ However, then it can be surprising that a button event
+ that now is the lock button event arrives (as retried postponed)
+ immediately after the state transits to <c>locked</c>.
+ </p>
+ <p>
+ So we make the <c>button/1</c> function synchronous
+ by using
+ <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>
+ and still postpone its events in the <c>open</c> state.
+ Then a call to <c>button/1</c> during the <c>open</c>
+ state does not return until the state transits to <c>locked</c>,
+ as it is there the event is handled and the reply is sent.
+ </p>
+ <p>
+ If a process now calls <c>set_lock_button/1</c>
+ to change the lock button while another process
+ hangs in <c>button/1</c> with the new lock button,
+ it can be expected that the hanging lock button call
+ immediately takes effect and locks the lock.
+ Therefore, we make the current lock button a part of the state,
+ so that when we change the lock button, the state changes
+ and all postponed events are retried.
+ </p>
+ <p>
+ We define the state as <c>{StateName,LockButton}</c>,
+ where <c>StateName</c> is as before
+ and <c>LockButton</c> is the current lock button:
+ </p>
+ <code type="erl"><![CDATA[
+-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([handle_event/4]).
+
+start_link(Code, LockButton) ->
+ 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, remaining => undefined, timer => undefined},
+ enter(?CALLBACK_MODE, {locked,LockButton}, Data, []).
+
+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,
+ {_StateName,_LockButton}, #{code := Code}) ->
+ {keep_state_and_data,
+ [{reply,From,length(Code)}]};
+handle_event(
+ 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(
+ 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]}.
+
+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) ->
+ {?CALLBACK_MODE,State,Data}.
+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.
+ ]]></code>
+ <p>
+ It can be an ill-fitting model for a physical code lock
+ that the <c>button/1</c> call can hang until the lock
+ is locked. But for an API in general it is not that strange.
+ </p>
+ </section>
+
+<!-- =================================================================== -->
+
+ <section>
+ <title>Hibernation</title>
+ <p>
+ If you have many servers in one node
+ and they have some state(s) in their lifetime in which
+ the servers can be expected to idle for a while,
+ and the amount of heap memory all these servers need
+ is a problem, then the memory footprint of a server
+ can be mimimized by hibernating it through
+ <seealso marker="stdlib:proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>.
+ </p>
+ <note>
+ <p>
+ It is rather costly to hibernate a process; see
+ <seealso marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seealso>.
+ It is not something you want to do after every event.
+ </p>
+ </note>
+ <p>
+ We can in this example hibernate in the <c>{open,_}</c> state,
+ because what normally occurs in that state is that
+ the state time-out after a while
+ triggers a transition to <c>{locked,_}</c>:
+ </p>
+ <code type="erl"><![CDATA[
+...
+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]};
+...
+ ]]></code>
+ <p>
+ The
+ <seealso marker="stdlib:gen_statem#type-hibernate"><c>[hibernate]</c></seealso>
+ action list on the last line
+ when entering the <c>{open,_}</c> state is the only change.
+ If any event arrives in the <c>{open,_},</c> state, we
+ do not bother to rehibernate, so the server stays
+ awake after any event.
+ </p>
+ <p>
+ To change that we would need to insert
+ action <c>hibernate</c> in more places.
+ For example, for the state-independent <c>set_lock_button</c>
+ and <c>code_length</c> operations that then would have to
+ be aware of using <c>hibernate</c> while in the
+ <c>{open,_}</c> state, which would clutter the code.
+ </p>
+ <p>
+ This server probably does not use
+ heap memory worth hibernating for.
+ To gain anything from hibernation, your server would
+ have to produce some garbage during callback execution,
+ for which this example server can serve as a bad example.
+ </p>
+ </section>
+
+</chapter>
diff --git a/system/doc/design_principles/sup_princ.xml b/system/doc/design_principles/sup_princ.xml
index 5e2f6ba9cb..0a24e97950 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>2014</year>
+ <year>1997</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -80,8 +80,8 @@ init(_Args) ->
</section>
<section>
- <title>Supervisor Flags</title>
<marker id="flags"/>
+ <title>Supervisor Flags</title>
<p>This is the type definition for the supervisor flags:</p>
<code type="none"><![CDATA[
sup_flags() = #{strategy => strategy(), % optional
@@ -106,9 +106,8 @@ sup_flags() = #{strategy => strategy(), % optional
</section>
<section>
- <marker id="strategy"></marker>
+ <marker id="strategy"/>
<title>Restart Strategy</title>
-
<p> The restart strategy is specified by
the <c>strategy</c> key in the supervisor flags map returned by
the callback function <c>init</c>:</p>
@@ -213,6 +212,7 @@ child_spec() = #{id => child_id(), % mandatory
<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,
see the <c>supervisor(3)</c> manual page.</item>
@@ -229,7 +229,7 @@ child_spec() = #{id => child_id(), % mandatory
is <c>rest_for_one</c> or <c>one_for_all</c> and a sibling
death causes the temporary process to be terminated).</item>
<item>A <c>transient</c> child process is restarted only if it
- terminates abnormally, that is, with another exit reason than
+ terminates abnormally, that is, with an exit reason other than
<c>normal</c>, <c>shutdown</c>, or <c>{shutdown,Term}</c>.</item>
</list>
<p>The <c>restart</c> key is optional. If it is not given, the
@@ -276,7 +276,8 @@ 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 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 <c>dynamic</c>.</p>
<p>This information is used by the release handler during
upgrades and downgrades, see
@@ -400,8 +401,8 @@ supervisor:delete_child(Sup, Id)</code>
restarts.</p>
</section>
- <marker id="simple"/>
<section>
+ <marker id="simple"/>
<title>Simplified one_for_one Supervisors</title>
<p>A supervisor with restart strategy <c>simple_one_for_one</c> is
a simplified <c>one_for_one</c> supervisor, where all child
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 \