aboutsummaryrefslogblamecommitdiffstats
path: root/system/doc/design_principles/fsm.xml
blob: 4f2b75e6e85114ccac36790e37a0d90c2f05723f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                       




                                       
                                        


                                                        










                                                                              


                  
                                    





                         
                                          


                                
                                                            





                                                                  


                                                                      

           

                                                                




                                                      


                                                                  


                                                                  
                                                               







                                              
                                                                  

                                                                     

                                                                           



                                                                       
                                        









                                    
                                                                               










                                                   
                                                  












                                                          


                                                                    

                      

                                                                               

                                                                        

                          






                                                                        


                                                                
                                                                     
                                    


                                                                        



                                                                   


                                                                        


                                                           

                                                                      

             
                                                                          

                                                                      




                                                                      





                                                                       

                                                                         
                                




                                                                         


            
                                         




                                                                   






                                                                       


                                                               
                             

















                                                                        

                                                                   


            
                            
                                                                   


                                                               




                                                                    







                                       
                                                                        























                                                                        




                                                                        

                                                                        


                                                                          















                                                             


                                                                        
















                                                                       
                                                                   
                                                                       

                                                                     
                                                              
                                     




                                          




                                                                          




                                                                          
                                                           



                                                         


            
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">

<chapter>
  <header>
    <copyright>
      <year>1997</year><year>2016</year>
      <holder>Ericsson AB. All Rights Reserved.</holder>
    </copyright>
    <legalnotice>
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
 
          http://www.apache.org/licenses/LICENSE-2.0

      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License.
    
    </legalnotice>

    <title>gen_fsm Behaviour</title>
    <prepared></prepared>
    <docno></docno>
    <date></date>
    <rev></rev>
    <file>fsm.xml</file>
  </header>
  <marker id="gen_fsm behaviour"></marker>
    <note>
      <p>
	There is a new behaviour
	<seealso marker="statem"><c>gen_statem</c></seealso>
	that is intended to replace <c>gen_fsm</c> for new code.
	It has the same features and add some really useful.
	This module will not be removed for the foreseeable future
	to keep old state machine implementations running.
      </p>
    </note>
  <p>This section is to be read with the <c>gen_fsm(3)</c> manual page
    in STDLIB, where all interface functions and callback
    functions are described in detail.</p>

  <section>
    <title>Finite-State Machines</title>
    <p>A Finite-State Machine (FSM) can be described as a set of
      relations of the form:</p>
    <pre>
State(S) x Event(E) -> Actions(A), State(S')</pre>
    <p>These relations are interpreted as meaning:</p>
    <quote>
      <p>If we are in state <c>S</c> and event <c>E</c> occurs, we
        are to perform actions <c>A</c> and make a transition to
        state <c>S'</c>.</p>
    </quote>
    <p>For an FSM implemented using the <c>gen_fsm</c> behaviour,
      the state transition rules are written as a number of Erlang
      functions, which conform to the following convention:</p>
    <pre>
StateName(Event, StateData) ->
    .. code for actions here ...
    {next_state, StateName', StateData'}</pre>
  </section>

  <section>
    <title>Example</title>
    <p>A door with a code lock can be viewed as an FSM. Initially,
      the door is locked. Anytime someone presses a button, this
      generates an event. Depending on what buttons have been pressed
      before, the sequence so far can be correct, incomplete, or wrong.</p>
    <p>If it is correct, the door is unlocked for 30 seconds (30,000 ms).
      If it is incomplete, we wait for another button to be pressed. If
      it is is wrong, we start all over, waiting for a new button
      sequence.</p>
    <p>Implementing the code lock FSM using <c>gen_fsm</c> results in
      the following callback module:</p>
    <marker id="ex"></marker>
    <code type="none"><![CDATA[
-module(code_lock).
-behaviour(gen_fsm).

-export([start_link/1]).
-export([button/1]).
-export([init/1, locked/2, open/2]).

start_link(Code) ->
    gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).

button(Digit) ->
    gen_fsm:send_event(code_lock, {button, Digit}).

init(Code) ->
    {ok, locked, {[], Code}}.

locked({button, Digit}, {SoFar, Code}) ->
    case [Digit|SoFar] of
        Code ->
            do_unlock(),
            {next_state, open, {[], Code}, 30000};
        Incomplete when length(Incomplete)<length(Code) ->
            {next_state, locked, {Incomplete, Code}};
        _Wrong ->
            {next_state, locked, {[], Code}}
    end.

open(timeout, State) ->
    do_lock(),
    {next_state, locked, State}.]]></code>
    <p>The code is explained in the next sections.</p>
  </section>

  <section>
    <title>Starting gen_fsm</title>
    <p>In the example in the previous section, the <c>gen_fsm</c> is
      started by calling <c>code_lock:start_link(Code)</c>:</p>
    <code type="none">
start_link(Code) ->
    gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).
    </code>
    <p><c>start_link</c> calls the function <c>gen_fsm:start_link/4</c>,
    which spawns and links to a new process, a <c>gen_fsm</c>.</p>
    <list type="bulleted">
      <item>
        <p>The first argument, <c>{local, code_lock}</c>, specifies
          the name. In this case, the <c>gen_fsm</c> is locally
	  registered as <c>code_lock</c>.</p>
        <p>If the name is omitted, the <c>gen_fsm</c> is not registered.
          Instead its pid must be used. The name can also be given
	  as <c>{global, Name}</c>, in which case the <c>gen_fsm</c> is
	  registered using <c>global:register_name/2</c>.</p>
      </item>
      <item>
        <p>The second argument, <c>code_lock</c>, is the name of
          the callback module, that is, the module where the callback
          functions are located.</p>
        <p>The interface functions (<c>start_link</c> and <c>button</c>)
	  are then located in the same module as the callback
          functions (<c>init</c>, <c>locked</c>, and <c>open</c>). This
          is normally good programming practice, to have the code
          corresponding to one process contained in one module.</p>
      </item>
      <item>
        <p>The third argument, <c>Code</c>, is a list of digits that
	  which is passed reversed to the callback function <c>init</c>.
	  Here, <c>init</c>
          gets the correct code for the lock as indata.</p>
      </item>
      <item>
        <p>The fourth argument, <c>[]</c>, is a list of options. See
          the <c>gen_fsm(3)</c> manual page for available options.</p>
      </item>
    </list>
    <p>If name registration succeeds, the new <c>gen_fsm</c> process calls
      the callback function <c>code_lock:init(Code)</c>. This function
      is expected to return <c>{ok, StateName, StateData}</c>, where
      <c>StateName</c> is the name of the initial state of the
      <c>gen_fsm</c>. In this case <c>locked</c>, assuming the door is
      locked to begin with. <c>StateData</c> is the internal state of
      the <c>gen_fsm</c>. (For <c>gen_fsm</c>, the internal state is
      often referred to 'state data' to
      distinguish it from the state as in states of a state machine.)
      In this case, the state data is the button sequence so far (empty
      to begin with) and the correct code of the lock.</p>
    <code type="none">
init(Code) ->
    {ok, locked, {[], Code}}.</code>
    <p><c>gen_fsm:start_link</c> is synchronous. It does not return until
    the <c>gen_fsm</c> has been initialized and is ready to
      receive notifications.</p>
    <p><c>gen_fsm:start_link</c> must be used if the <c>gen_fsm</c> is
      part of a supervision tree, that is, started by a supervisor. There
      is another function, <c>gen_fsm:start</c>, to start a standalone
      <c>gen_fsm</c>, that is, a <c>gen_fsm</c> that is not part of a
      supervision tree.</p>
  </section>

  <section>
    <title>Notifying about Events</title>
    <p>The function notifying the code lock about a button event is
      implemented using <c>gen_fsm:send_event/2</c>:</p>
    <code type="none">
button(Digit) ->
    gen_fsm:send_event(code_lock, {button, Digit}).</code>
    <p><c>code_lock</c> is the name of the <c>gen_fsm</c> and must
      agree with the name used to start it.
      <c>{button, Digit}</c> is the actual event.</p>
    <p>The event is made into a message and sent to the <c>gen_fsm</c>.
      When the event is received, the <c>gen_fsm</c> calls
      <c>StateName(Event, StateData)</c>, which is expected to return a
      tuple <c>{next_state,StateName1,StateData1}</c>.
      <c>StateName</c> is the name of the current state and
      <c>StateName1</c> is the name of the next state to go to.
      <c>StateData1</c> is a new value for the state data of
      the <c>gen_fsm</c>.</p>
    <code type="none"><![CDATA[
locked({button, Digit}, {SoFar, Code}) ->
    case [Digit|SoFar] of
        Code ->
            do_unlock(),
            {next_state, open, {[], Code}, 30000};
        Incomplete when length(Incomplete)<length(Code) ->
            {next_state, locked, {Incomplete, Code}};
        _Wrong ->
            {next_state, locked, {[], Code}};
    end.

open(timeout, State) ->
    do_lock(),
    {next_state, locked, State}.]]></code>
    <p>If the door is locked and a button is pressed, the complete
      button sequence so far is compared with the correct code for
      the lock and, depending on the result, the door is either unlocked
      and the <c>gen_fsm</c> goes to state <c>open</c>, or the door
      remains in state <c>locked</c>.</p>
  </section>

  <section>
    <title>Time-Outs</title>
    <p>When a correct code has been given, the door is unlocked and
      the following tuple is returned from <c>locked/2</c>:</p>
    <code type="none">
{next_state, open, {[], Code}, 30000};</code>
    <p>30,000 is a time-out value in milliseconds. After this time,
      that is, 30 seconds, a time-out occurs. Then,
      <c>StateName(timeout, StateData)</c> is called. The time-out
      then occurs when the door has been in state <c>open</c> for 30
      seconds. After that the door is locked again:</p>
    <code type="none">
open(timeout, State) ->
    do_lock(),
    {next_state, locked, State}.</code>
  </section>

  <section>
    <title>All State Events</title>
    <p>Sometimes an event can arrive at any state of the <c>gen_fsm</c>.
      Instead of sending the message with <c>gen_fsm:send_event/2</c>
      and writing one clause handling the event for each state function,
      the message can be sent with <c>gen_fsm:send_all_state_event/2</c>
      and handled with <c>Module:handle_event/3</c>:</p>
    <code type="none">
-module(code_lock).
...
-export([stop/0]).
...

stop() ->
    gen_fsm:send_all_state_event(code_lock, stop).

...

handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.</code>
  </section>

  <section>
    <title>Stopping</title>

    <section>
      <title>In a Supervision Tree</title>
      <p>If the <c>gen_fsm</c> is part of a supervision tree, no stop
        function is needed. The <c>gen_fsm</c> is automatically
	terminated by its supervisor. Exactly how this is done is
	defined by a
	<seealso marker="sup_princ#shutdown">shutdown strategy</seealso>
        set in the supervisor.</p>
      <p>If it is necessary to clean up before termination, the shutdown
        strategy must be a time-out value and the <c>gen_fsm</c> must be
	set to trap exit signals in the <c>init</c> function. When ordered
        to shutdown, the <c>gen_fsm</c> then calls the callback function
        <c>terminate(shutdown, StateName, StateData)</c>:</p>
      <code type="none">
init(Args) ->
    ...,
    process_flag(trap_exit, true),
    ...,
    {ok, StateName, StateData}.

...

terminate(shutdown, StateName, StateData) ->
    ..code for cleaning up here..
    ok.</code>
    </section>

    <section>
      <title>Standalone gen_fsm</title>
      <p>If the <c>gen_fsm</c> is not part of a supervision tree, a stop
        function can be useful, for example:</p>
      <code type="none">
...
-export([stop/0]).
...

stop() ->
    gen_fsm:send_all_state_event(code_lock, stop).
...

handle_event(stop, _StateName, StateData) ->
    {stop, normal, StateData}.

...

terminate(normal, _StateName, _StateData) ->
    ok.</code>
      <p>The callback function handling the <c>stop</c> event returns a
        tuple, <c>{stop,normal,StateData1}</c>, where <c>normal</c>
        specifies that it is a normal termination and <c>StateData1</c>
        is a new value for the state data of the <c>gen_fsm</c>. This
        causes the <c>gen_fsm</c> to call
        <c>terminate(normal,StateName,StateData1)</c> and then
        it terminates gracefully:</p>
    </section>
  </section>

  <section>
    <title>Handling Other Messages</title>
    <p>If the <c>gen_fsm</c> is to be able to receive other messages
      than events, the callback function
      <c>handle_info(Info, StateName, StateData)</c> must be implemented
      to handle them. Examples of
      other messages are exit messages, if the <c>gen_fsm</c> is linked to
      other processes (than the supervisor) and trapping exit signals.</p>
    <code type="none">
handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
    ..code to handle exits here..
    {next_state, StateName1, StateData1}.</code>
    <p>The code_change method must also be implemented.</p>
    <code type="none">
code_change(OldVsn, StateName, StateData, Extra) ->
    ..code to convert state (and more) during code change
    {ok, NextStateName, NewStateData}</code>
  </section>
</chapter>