1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- This document was generated using DocBuilder 3.3.3 -->
<HTML>
<HEAD>
<TITLE>Gen_Fsm Behaviour</TITLE>
<SCRIPT type="text/javascript" src="../../doc/erlresolvelinks.js">
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#FF00FF"
ALINK="#FF0000">
<CENTER>
<A HREF="http://www.erlang.se"><IMG BORDER=0 ALT="[Ericsson AB]" SRC="min_head.gif"></A>
</CENTER>
<A NAME="3"><!-- Empty --></A>
<H2>3 Gen_Fsm Behaviour</H2>
<P>This chapter should be read in conjunction with <CODE>gen_fsm(3)</CODE>,
where all interface functions and callback functions are described
in detail.<A NAME="3.1"><!-- Empty --></A>
<H3>3.1 Finite State Machines</H3>
<P>A finite state machine, FSM, can be described as a set of
relations of the form:
<PRE>
State(S) x Event(E) -> Actions(A), State(S')
</PRE>
<P>These relations are interpreted as meaning:
<BLOCKQUOTE>
<P>If we are in state <CODE>S</CODE> and the event <CODE>E</CODE> occurs, we
should perform the actions <CODE>A</CODE> and make a transition to
the state <CODE>S'</CODE>.
</BLOCKQUOTE>
<P>For an FSM implemented using the <CODE>gen_fsm</CODE> behaviour,
the state transition rules are written as a number of Erlang
functions which conform to the following convention:
<PRE>
StateName(Event, StateData) ->
.. code for actions here ...
{next_state, StateName', StateData'}
</PRE>
<A NAME="3.2"><!-- Empty --></A>
<H3>3.2 Example</H3>
<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>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>Implementing the code lock FSM using <CODE>gen_fsm</CODE> results in
this callback module:<A NAME="ex"><!-- Empty --></A>
<PRE>
-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}.
</PRE>
<P>The code is explained in the next sections.<A NAME="3.3"><!-- Empty --></A>
<H3>3.3 Starting a Gen_Fsm</H3>
<P>In the example in the previous section, the gen_fsm is started by
calling <CODE>code_lock:start_link(Code)</CODE>:
<PRE>
start_link(Code) ->
gen_fsm:start_link({local, code_lock}, code_lock, Code, []).
</PRE>
<P><CODE>start_link</CODE> calls the function <CODE>gen_fsm:start_link/4</CODE>.
This function spawns and links to a new process, a gen_fsm.
<P>
<UL>
<LI>
The first argument <CODE>{local, code_lock}</CODE> specifies
the name. In this case, the gen_fsm will be locally registered
as <CODE>code_lock</CODE>.<BR>
If the name is omitted, the gen_fsm is not registered.
Instead its pid must be used. The name could also be given as
<CODE>{global, Name}</CODE>, in which case the gen_fsm is registered
using <CODE>global:register_name/2</CODE>.<BR>
</LI>
<LI>
The second argument, <CODE>code_lock</CODE>, is the name of
the callback module, that is the module where the callback
functions are located.<BR>
In this case, the interface functions (<CODE>start_link</CODE> and
<CODE>button</CODE>) are located in the same module as the callback
functions (<CODE>init</CODE>, <CODE>locked</CODE> and <CODE>open</CODE>). This
is normally good programming practice, to have the code
corresponding to one process contained in one module.<BR>
</LI>
<LI>
The third argument, <CODE>Code</CODE>, is a term which is passed
as-is to the callback function <CODE>init</CODE>. Here, <CODE>init</CODE>
gets the correct code for the lock as indata.<BR>
</LI>
<LI>
The fourth argument, [], is a list of options. See
<CODE>gen_fsm(3)</CODE> for available options.<BR>
</LI>
</UL>
<P>If name registration succeeds, the new gen_fsm process calls
the callback function <CODE>code_lock:init(Code)</CODE>. This function
is expected to return <CODE>{ok, StateName, StateData}</CODE>, where
<CODE>StateName</CODE> is the name of the initial state of the gen_fsm.
In this case <CODE>locked</CODE>, assuming the door is locked to begin
with. <CODE>StateData</CODE> 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.
<PRE>
init(Code) ->
{ok, locked, {[], Code}}.
</PRE>
<P>Note that <CODE>gen_fsm:start_link</CODE> is synchronous. It does not
return until the gen_fsm has been initialized and is ready to
receive notifications.
<P><CODE>gen_fsm:start_link</CODE> must be used if the gen_fsm is part of
a supervision tree, i.e. is started by a supervisor. There is
another function <CODE>gen_fsm:start</CODE> to start a stand-alone
gen_fsm, i.e. a gen_fsm which is not part of a supervision tree.
<A NAME="3.4"><!-- Empty --></A>
<H3>3.4 Notifying About Events</H3>
<P>The function notifying the code lock about a button event is
implemented using <CODE>gen_fsm:send_event/2</CODE>:
<PRE>
button(Digit) ->
gen_fsm:send_event(code_lock, {button, Digit}).
</PRE>
<P><CODE>code_lock</CODE> is the name of the gen_fsm and must agree with
the name used to start it. <CODE>{button, Digit}</CODE> is the actual
event.
<P>The event is made into a message and sent to the gen_fsm. When
the event is received, the gen_fsm calls
<CODE>StateName(Event, StateData)</CODE> which is expected to return a
tuple <CODE>{next_state, StateName1, StateData1}</CODE>.
<CODE>StateName</CODE> is the name of the current state and
<CODE>StateName1</CODE> is the name of the next state to go to.
<CODE>StateData1</CODE> is a new value for the state data of
the gen_fsm.
<PRE>
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}.
</PRE>
<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 <CODE>open</CODE>, or the door remains in
state <CODE>locked</CODE>.<A NAME="3.5"><!-- Empty --></A>
<H3>3.5 Timeouts</H3>
<P>When a correct code has been givened, the door is unlocked and
the following tuple is returned from <CODE>locked/2</CODE>:
<PRE>
{next_state, open, {[], Code}, 30000};
</PRE>
<P>30000 is a timeout value in milliseconds. After 30000 ms, i.e.
30 seconds, a timeout occurs. Then <CODE>StateName(timeout,
StateData)</CODE> is called. In this case, the timeout occurs when
the door has been in state <CODE>open</CODE> for 30 seconds. After that
the door is locked again:
<PRE>
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.
</PRE>
<A NAME="3.6"><!-- Empty --></A>
<H3>3.6 All State Events</H3>
<P>Sometimes an event can arrive at any state of the gen_fsm.
Instead of sending the message with <CODE>gen_fsm:send_event/2</CODE>
and writing one clause handling the event for each state function,
the message can be sent with <CODE>gen_fsm:send_all_state_event/2</CODE>
and handled with <CODE>Module:handle_event/3</CODE>:
<PRE>
-module(code_lock).
...
-export([stop/0]).
...
stop() ->
gen_fsm:send_all_state_event(code_lock, stop).
...
handle_event(stop, _StateName, StateData) ->
{stop, normal, StateData}.
</PRE>
<A NAME="3.7"><!-- Empty --></A>
<H3>3.7 Stopping</H3>
<A NAME="3.7.1"><!-- Empty --></A>
<H4>3.7.1 In a Supervision Tree</H4>
<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
<A HREF="sup_princ.html#shutdown">shutdown strategy</A>
set in the supervisor.
<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 <CODE>init</CODE> function. When ordered
to shutdown, the gen_fsm will then call the callback function
<CODE>terminate(shutdown, StateName, StateData)</CODE>:
<PRE>
init(Args) ->
...,
process_flag(trap_exit, true),
...,
{ok, StateName, StateData}.
...
terminate(shutdown, StateName, StateData) ->
..code for cleaning up here..
ok.
</PRE>
<A NAME="3.7.2"><!-- Empty --></A>
<H4>3.7.2 Stand-Alone Gen_Fsms</H4>
<P>If the gen_fsm is not part of a supervision tree, a stop
function may be useful, for example:
<PRE>
...
-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.
</PRE>
<P>The callback function handling the <CODE>stop</CODE> event returns a
tuple <CODE>{stop,normal,StateData1}</CODE>, where <CODE>normal</CODE>
specifies that it is a normal termination and <CODE>StateData1</CODE>
is a new value for the state data of the gen_fsm. This will
cause the gen_fsm to call
<CODE>terminate(normal,StateName,StateData1)</CODE> and then
terminate gracefully:<A NAME="3.8"><!-- Empty --></A>
<H3>3.8 Handling Other Messages</H3>
<P>If the gen_fsm should be able to receive other messages than
events, the callback function <CODE>handle_info(Info, StateName,
StateData)</CODE> 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.
<PRE>
handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
..code to handle exits here..
{next_state, StateName1, StateData1}.
</PRE>
<CENTER>
<HR>
<SMALL>
Copyright © 1991-2006
<A HREF="http://www.erlang.se">Ericsson AB</A><BR>
</SMALL>
</CENTER>
</BODY>
</HTML>
|