diff options
Diffstat (limited to 'system/doc/design_principles')
21 files changed, 1534 insertions, 27 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 Binary files differnew file mode 100644 index 0000000000..8e6ff8a898 --- /dev/null +++ b/system/doc/design_principles/code_lock.dia diff --git a/system/doc/design_principles/code_lock.png b/system/doc/design_principles/code_lock.png Binary files differnew file mode 100644 index 0000000000..745fd91920 --- /dev/null +++ b/system/doc/design_principles/code_lock.png diff --git a/system/doc/design_principles/code_lock_2.dia b/system/doc/design_principles/code_lock_2.dia Binary files differnew file mode 100644 index 0000000000..142909a2f5 --- /dev/null +++ b/system/doc/design_principles/code_lock_2.dia diff --git a/system/doc/design_principles/code_lock_2.png b/system/doc/design_principles/code_lock_2.png Binary files differnew file mode 100644 index 0000000000..ecf7b0d799 --- /dev/null +++ b/system/doc/design_principles/code_lock_2.png 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..585b1a35f5 --- /dev/null +++ b/system/doc/design_principles/statem.xml @@ -0,0 +1,1454 @@ +<?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 dispatches + on state name for you. This encourages the callback module + to gather the implementation of all event actions particular + to one state in the same place in the code, + hence 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 introducing dispatching. + </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>. Also, as you do the dispatching + yourself, you make <c>StateName</c> decide where in the code + to handle most events in the state. + The second element of the tuple is then used to select + whether to handle special client-side or server-side events. + </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 dispatch on event first and then 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 waits 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 clean first-dispatch-on-event approach + does not work that well here because of the generated + entry actions so this example dispatches on state first: + </p> + <code type="erl"><![CDATA[ +... +-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..a77b3964fc 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> @@ -213,6 +213,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 +230,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 +277,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 +402,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 \ |