19972013
Ericsson AB. 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.
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.
gen_fsm Behaviour
fsm.xml
This section is to be read with the gen_fsm(3) manual page
in STDLIB, where all interface functions and callback
functions are described in detail.
Finite-State Machines
A Finite-State Machine (FSM) can be described as a set of
relations of the form:
State(S) x Event(E) -> Actions(A), State(S')
These relations are interpreted as meaning:
If we are in state S and event E occurs, we
are to perform actions A and make a transition to
state S'.
For an FSM implemented using the gen_fsm behaviour,
the state transition rules are written as a number of Erlang
functions, which conform to the following convention:
StateName(Event, StateData) ->
.. code for actions here ...
{next_state, StateName', StateData'}
Example
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.
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.
Implementing the code lock FSM using gen_fsm results in
the following callback module:
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)
{next_state, locked, {Incomplete, Code}};
_Wrong ->
{next_state, locked, {[], Code}}
end.
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.]]>
The code is explained in the next sections.
Starting gen_fsm
In the example in the previous section, the gen_fsm is
started by calling code_lock:start_link(Code):
start_link(Code) ->
gen_fsm:start_link({local, code_lock}, code_lock, lists:reverse(Code), []).
start_link calls the function gen_fsm:start_link/4,
which spawns and links to a new process, a gen_fsm.
-
The first argument, {local, code_lock}, specifies
the name. In this case, the gen_fsm is locally
registered as code_lock.
If the name is omitted, the gen_fsm is not registered.
Instead its pid must be used. The name can also be given
as {global, Name}, in which case the gen_fsm is
registered using global:register_name/2.
-
The second argument, code_lock, is the name of
the callback module, that is, the module where the callback
functions are located.
The interface functions (start_link and button)
are then located in the same module as the callback
functions (init, locked, and open). This
is normally good programming practice, to have the code
corresponding to one process contained in one module.
-
The third argument, Code, is a list of digits that
which is passed reversed to the callback function init.
Here, init
gets the correct code for the lock as indata.
-
The fourth argument, [], is a list of options. See
the gen_fsm(3) manual page for available options.
If name registration succeeds, the new gen_fsm process calls
the callback function code_lock:init(Code). This function
is expected to return {ok, StateName, StateData}, where
StateName is the name of the initial state of the
gen_fsm. In this case locked, assuming the door is
locked to begin with. StateData is the internal state of
the gen_fsm. (For gen_fsm, 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.
init(Code) ->
{ok, locked, {[], Code}}.
gen_fsm:start_link is synchronous. It does not return until
the gen_fsm has been initialized and is ready to
receive notifications.
gen_fsm:start_link must be used if the gen_fsm is
part of a supervision tree, that is, started by a supervisor. There
is another function, gen_fsm:start, to start a standalone
gen_fsm, that is, a gen_fsm that is not part of a
supervision tree.
Notifying about Events
The function notifying the code lock about a button event is
implemented using gen_fsm:send_event/2:
button(Digit) ->
gen_fsm:send_event(code_lock, {button, Digit}).
code_lock is the name of the gen_fsm and must
agree with the name used to start it.
{button, Digit} is the actual event.
The event is made into a message and sent to the gen_fsm.
When the event is received, the gen_fsm calls
StateName(Event, StateData), which is expected to return a
tuple {next_state,StateName1,StateData1}.
StateName is the name of the current state and
StateName1 is the name of the next state to go to.
StateData1 is a new value for the state data of
the gen_fsm.
case [Digit|SoFar] of
Code ->
do_unlock(),
{next_state, open, {[], Code}, 30000};
Incomplete when length(Incomplete)
{next_state, locked, {Incomplete, Code}};
_Wrong ->
{next_state, locked, {[], Code}};
end.
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.]]>
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 open, or the door
remains in state locked.
Time-Outs
When a correct code has been given, the door is unlocked and
the following tuple is returned from locked/2:
{next_state, open, {[], Code}, 30000};
30,000 is a time-out value in milliseconds. After this time,
that is, 30 seconds, a time-out occurs. Then,
StateName(timeout, StateData) is called. The time-out
then occurs when the door has been in state open for 30
seconds. After that the door is locked again:
open(timeout, State) ->
do_lock(),
{next_state, locked, State}.
All State Events
Sometimes an event can arrive at any state of the gen_fsm.
Instead of sending the message with gen_fsm:send_event/2
and writing one clause handling the event for each state function,
the message can be sent with gen_fsm:send_all_state_event/2
and handled with Module:handle_event/3:
-module(code_lock).
...
-export([stop/0]).
...
stop() ->
gen_fsm:send_all_state_event(code_lock, stop).
...
handle_event(stop, _StateName, StateData) ->
{stop, normal, StateData}.
Stopping
In a Supervision Tree
If the gen_fsm is part of a supervision tree, no stop
function is needed. The gen_fsm is automatically
terminated by its supervisor. Exactly how this is done is
defined by a
shutdown strategy
set in the supervisor.
If it is necessary to clean up before termination, the shutdown
strategy must be a time-out value and the gen_fsm must be
set to trap exit signals in the init function. When ordered
to shutdown, the gen_fsm then calls the callback function
terminate(shutdown, StateName, StateData):
init(Args) ->
...,
process_flag(trap_exit, true),
...,
{ok, StateName, StateData}.
...
terminate(shutdown, StateName, StateData) ->
..code for cleaning up here..
ok.
Standalone gen_fsm
If the gen_fsm is not part of a supervision tree, a stop
function can be useful, for example:
...
-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.
The callback function handling the stop event returns a
tuple, {stop,normal,StateData1}, where normal
specifies that it is a normal termination and StateData1
is a new value for the state data of the gen_fsm. This
causes the gen_fsm to call
terminate(normal,StateName,StateData1) and then
it terminates gracefully:
Handling Other Messages
If the gen_fsm is to be able to receive other messages
than events, the callback function
handle_info(Info, StateName, StateData) 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.
handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
..code to handle exits here..
{next_state, StateName1, StateData1}.
The code_change method must also be implemented.
code_change(OldVsn, StateName, StateData, Extra) ->
..code to convert state (and more) during code change
{ok, NextStateName, NewStateData}