aboutsummaryrefslogtreecommitdiffstats
path: root/system/doc/design_principles/fsm.xml
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /system/doc/design_principles/fsm.xml
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'system/doc/design_principles/fsm.xml')
-rw-r--r--system/doc/design_principles/fsm.xml313
1 files changed, 313 insertions, 0 deletions
diff --git a/system/doc/design_principles/fsm.xml b/system/doc/design_principles/fsm.xml
new file mode 100644
index 0000000000..7cdd62057b
--- /dev/null
+++ b/system/doc/design_principles/fsm.xml
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1997</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ </legalnotice>
+
+ <title>Gen_Fsm Behaviour</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>fsm.xml</file>
+ </header>
+ <p>This chapter should be read in conjunction with <c>gen_fsm(3)</c>,
+ where all interface functions and callback functions are described
+ in detail.</p>
+
+ <section>
+ <title>Finite State Machines</title>
+ <p>A finite state machine, FSM, can be described as a set of
+ relations of the form:</p>
+ <pre>
+State(S) x Event(E) -> Actions(A), State(S')</pre>
+ <p>These relations are interpreted as meaning:</p>
+ <quote>
+ <p>If we are in state <c>S</c> and the event <c>E</c> occurs, we
+ should perform the actions <c>A</c> and make a transition to
+ the state <c>S'</c>.</p>
+ </quote>
+ <p>For an FSM implemented using the <c>gen_fsm</c> behaviour,
+ the state transition rules are written as a number of Erlang
+ functions which conform to the following convention:</p>
+ <pre>
+StateName(Event, StateData) ->
+ .. code for actions here ...
+ {next_state, StateName', StateData'}</pre>
+ </section>
+
+ <section>
+ <title>Example</title>
+ <p>A door with a code lock could be viewed as an FSM. Initially,
+ the door is locked. Anytime someone presses a button, this
+ generates an event. Depending on what buttons have been pressed
+ before, the sequence so far may be correct, incomplete or wrong.</p>
+ <p>If it is correct, the door is unlocked for 30 seconds (30000 ms).
+ If it is incomplete, we wait for another button to be pressed. If
+ it is is wrong, we start all over, waiting for a new button
+ sequence.</p>
+ <p>Implementing the code lock FSM using <c>gen_fsm</c> results in
+ this callback module:</p>
+ <marker id="ex"></marker>
+ <code type="none"><![CDATA[
+-module(code_lock).
+-behaviour(gen_fsm).
+
+-export([start_link/1]).
+-export([button/1]).
+-export([init/1, locked/2, open/2]).
+
+start_link(Code) ->
+ gen_fsm:start_link({local, code_lock}, code_lock, Code, []).
+
+button(Digit) ->
+ gen_fsm:send_event(code_lock, {button, Digit}).
+
+init(Code) ->
+ {ok, locked, {[], Code}}.
+
+locked({button, Digit}, {SoFar, Code}) ->
+ case [Digit|SoFar] of
+ Code ->
+ do_unlock(),
+ {next_state, open, {[], Code}, 3000};
+ Incomplete when length(Incomplete)<length(Code) ->
+ {next_state, locked, {Incomplete, Code}};
+ _Wrong ->
+ {next_state, locked, {[], Code}}
+ end.
+
+open(timeout, State) ->
+ do_lock(),
+ {next_state, locked, State}.]]></code>
+ <p>The code is explained in the next sections.</p>
+ </section>
+
+ <section>
+ <title>Starting a Gen_Fsm</title>
+ <p>In the example in the previous section, the gen_fsm is started by
+ calling <c>code_lock:start_link(Code)</c>:</p>
+ <code type="none">
+start_link(Code) ->
+ gen_fsm:start_link({local, code_lock}, code_lock, Code, []).</code>
+ <p><c>start_link</c> calls the function <c>gen_fsm:start_link/4</c>.
+ This function spawns and links to a new process, a gen_fsm.</p>
+ <list type="bulleted">
+ <item>
+ <p>The first argument <c>{local, code_lock}</c> specifies
+ the name. In this case, the gen_fsm will be locally registered
+ as <c>code_lock</c>.</p>
+ <p>If the name is omitted, the gen_fsm is not registered.
+ Instead its pid must be used. The name could also be given as
+ <c>{global, Name}</c>, in which case the gen_fsm is registered
+ using <c>global:register_name/2</c>.</p>
+ </item>
+ <item>
+ <p>The second argument, <c>code_lock</c>, is the name of
+ the callback module, that is the module where the callback
+ functions are located.</p>
+ <p>In this case, the interface functions (<c>start_link</c> and
+ <c>button</c>) are located in the same module as the callback
+ functions (<c>init</c>, <c>locked</c> and <c>open</c>). This
+ is normally good programming practice, to have the code
+ corresponding to one process contained in one module.</p>
+ </item>
+ <item>
+ <p>The third argument, <c>Code</c>, is a term which is passed
+ as-is to the callback function <c>init</c>. Here, <c>init</c>
+ gets the correct code for the lock as indata.</p>
+ </item>
+ <item>
+ <p>The fourth argument, [], is a list of options. See
+ <c>gen_fsm(3)</c> for available options.</p>
+ </item>
+ </list>
+ <p>If name registration succeeds, the new gen_fsm process calls
+ the callback function <c>code_lock:init(Code)</c>. This function
+ is expected to return <c>{ok, StateName, StateData}</c>, where
+ <c>StateName</c> is the name of the initial state of the gen_fsm.
+ In this case <c>locked</c>, assuming the door is locked to begin
+ with. <c>StateData</c> is the internal state of the gen_fsm. (For
+ gen_fsms, the internal state is often referred to 'state data' to
+ distinguish it from the state as in states of a state machine.)
+ In this case, the state data is the button sequence so far (empty
+ to begin with) and the correct code of the lock.</p>
+ <code type="none">
+init(Code) ->
+ {ok, locked, {[], Code}}.</code>
+ <p>Note that <c>gen_fsm:start_link</c> is synchronous. It does not
+ return until the gen_fsm has been initialized and is ready to
+ receive notifications.</p>
+ <p><c>gen_fsm:start_link</c> must be used if the gen_fsm is part of
+ a supervision tree, i.e. is started by a supervisor. There is
+ another function <c>gen_fsm:start</c> to start a stand-alone
+ gen_fsm, i.e. a gen_fsm which is not part of a supervision tree.</p>
+ </section>
+
+ <section>
+ <title>Notifying About Events</title>
+ <p>The function notifying the code lock about a button event is
+ implemented using <c>gen_fsm:send_event/2</c>:</p>
+ <code type="none">
+button(Digit) ->
+ gen_fsm:send_event(code_lock, {button, Digit}).</code>
+ <p><c>code_lock</c> is the name of the gen_fsm and must agree with
+ the name used to start it. <c>{button, Digit}</c> is the actual
+ event.</p>
+ <p>The event is made into a message and sent to the gen_fsm. When
+ the event is received, the gen_fsm calls
+ <c>StateName(Event, StateData)</c> which is expected to return a
+ tuple <c>{next_state, StateName1, StateData1}</c>.
+ <c>StateName</c> is the name of the current state and
+ <c>StateName1</c> is the name of the next state to go to.
+ <c>StateData1</c> is a new value for the state data of
+ the gen_fsm.</p>
+ <code type="none"><![CDATA[
+locked({button, Digit}, {SoFar, Code}) ->
+ case [Digit|SoFar] of
+ Code ->
+ do_unlock(),
+ {next_state, open, {[], Code}, 30000};
+ Incomplete when length(Incomplete)<length(Code) ->
+ {next_state, locked, {Incomplete, Code}};
+ _Wrong ->
+ {next_state, locked, {[], Code}};
+ end.
+
+open(timeout, State) ->
+ do_lock(),
+ {next_state, locked, State}.]]></code>
+ <p>If the door is locked and a button is pressed, the complete
+ button sequence so far is compared with the correct code for
+ the lock and, depending on the result, the door is either unlocked
+ and the gen_fsm goes to state <c>open</c>, or the door remains in
+ state <c>locked</c>.</p>
+ </section>
+
+ <section>
+ <title>Timeouts</title>
+ <p>When a correct code has been givened, the door is unlocked and
+ the following tuple is returned from <c>locked/2</c>:</p>
+ <code type="none">
+{next_state, open, {[], Code}, 30000};</code>
+ <p>30000 is a timeout value in milliseconds. After 30000 ms, i.e.
+ 30 seconds, a timeout occurs. Then <c>StateName(timeout, StateData)</c> is called. In this case, the timeout occurs when
+ the door has been in state <c>open</c> for 30 seconds. After that
+ the door is locked again:</p>
+ <code type="none">
+open(timeout, State) ->
+ do_lock(),
+ {next_state, locked, State}.</code>
+ </section>
+
+ <section>
+ <title>All State Events</title>
+ <p>Sometimes an event can arrive at any state of the gen_fsm.
+ Instead of sending the message with <c>gen_fsm:send_event/2</c>
+ and writing one clause handling the event for each state function,
+ the message can be sent with <c>gen_fsm:send_all_state_event/2</c>
+ and handled with <c>Module:handle_event/3</c>:</p>
+ <code type="none">
+-module(code_lock).
+...
+-export([stop/0]).
+...
+
+stop() ->
+ gen_fsm:send_all_state_event(code_lock, stop).
+
+...
+
+handle_event(stop, _StateName, StateData) ->
+ {stop, normal, StateData}.</code>
+ </section>
+
+ <section>
+ <title>Stopping</title>
+
+ <section>
+ <title>In a Supervision Tree</title>
+ <p>If the gen_fsm is part of a supervision tree, no stop function
+ is needed. The gen_fsm will automatically be 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 timeout value and the gen_fsm must be set to
+ trap exit signals in the <c>init</c> function. When ordered
+ to shutdown, the gen_fsm will then call the callback function
+ <c>terminate(shutdown, StateName, StateData)</c>:</p>
+ <code type="none">
+init(Args) ->
+ ...,
+ process_flag(trap_exit, true),
+ ...,
+ {ok, StateName, StateData}.
+
+...
+
+terminate(shutdown, StateName, StateData) ->
+ ..code for cleaning up here..
+ ok.</code>
+ </section>
+
+ <section>
+ <title>Stand-Alone Gen_Fsms</title>
+ <p>If the gen_fsm is not part of a supervision tree, a stop
+ function may be useful, for example:</p>
+ <code type="none">
+...
+-export([stop/0]).
+...
+
+stop() ->
+ gen_fsm:send_all_state_event(code_lock, stop).
+...
+
+handle_event(stop, _StateName, StateData) ->
+ {stop, normal, StateData}.
+
+...
+
+terminate(normal, _StateName, _StateData) ->
+ ok.</code>
+ <p>The callback function handling the <c>stop</c> event returns a
+ tuple <c>{stop,normal,StateData1}</c>, where <c>normal</c>
+ specifies that it is a normal termination and <c>StateData1</c>
+ is a new value for the state data of the gen_fsm. This will
+ cause the gen_fsm to call
+ <c>terminate(normal,StateName,StateData1)</c> and then
+ terminate gracefully:</p>
+ </section>
+ </section>
+
+ <section>
+ <title>Handling Other Messages</title>
+ <p>If the gen_fsm should be able to receive other messages than
+ events, the callback function <c>handle_info(Info, StateName, StateData)</c> must be implemented to handle them. Examples of
+ other messages are exit messages, if the gen_fsm is linked to
+ other processes (than the supervisor) and trapping exit signals.</p>
+ <code type="none">
+handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
+ ..code to handle exits here..
+ {next_state, StateName1, StateData1}.</code>
+ </section>
+</chapter>
+