gen_fsm(3)
, where all interface functions and callback functions are described in detail.
java 代码
- State(S) x Event(E) -> Actions(A), State(S')
These relations are interpreted as meaning:
If we are in state
S
and the eventE
occurs, we should perform the actionsA
and make a transition to the stateS'
.
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:
java 代码
- StateName(Event, StateData) ->
- .. code for actions here ...
- {next_state, StateName', StateData'}
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.
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.
Implementing the code lock FSM using gen_fsm
results in this callback module:
java 代码
- -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)
- {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.
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, Code, []).
start_link
calls the function gen_fsm:start_link/4
. This function spawns and links to a new process, a gen_fsm.
{local, code_lock}
specifies the name. In this case, the gen_fsm will be locally registered as code_lock
.{global, Name}
, in which case the gen_fsm is registered using global:register_name/2
.code_lock
, is the name of the callback module, that is the module where the callback functions are located.start_link
and button
) are 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.Code
, is a term which is passed as-is to the callback function init
. Here, init
gets the correct code for the lock as indata.gen_fsm(3)
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_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.
java 代码
- init(Code) ->
- {ok, locked, {[], Code}}.
Note that 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, i.e. is started by a supervisor. There is another function gen_fsm:start
to start a stand-alone gen_fsm, i.e. a gen_fsm which is not part of a supervision tree.
The function notifying the code lock about a button event is implemented using gen_fsm:send_event/2
:
java 代码
- 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.
java 代码
- 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}.
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
.
When a correct code has been givened, the door is unlocked and the following tuple is returned from locked/2
:
java 代码
- {next_state, open, {[], Code}, 30000};
30000 is a timeout value in milliseconds. After 30000 ms, i.e. 30 seconds, a timeout occurs. Then StateName(timeout, StateData)
is called. In this case, the timeout occurs when the door has been in state open
for 30 seconds. After that the door is locked again:
java 代码
- open(timeout, State) ->
- do_lock(),
- {next_state, locked, State}.
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
:
java 代码
- -module(code_lock).
- ...
- -export([stop/0]).
- ...
- stop() ->
- gen_fsm:send_all_state_event(code_lock, stop).
- ...
- handle_event(stop, _StateName, StateData) ->
- {stop, normal, StateData}.
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 shutdown strategy set in the supervisor.
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 init
function. When ordered to shutdown, the gen_fsm will then call the callback function terminate(shutdown, StateName, StateData)
:
java 代码
- init(Args) ->
- ...,
- process_flag(trap_exit, true),
- ...,
- {ok, StateName, StateData}.
- ...
- terminate(shutdown, StateName, StateData) ->
- ..code for cleaning up here..
- ok.
If the gen_fsm is not part of a supervision tree, a stop function may be useful, for example:
java 代码
- ...
- -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 will cause the gen_fsm to call terminate(normal,StateName,StateData1)
and then terminate gracefully:
If the gen_fsm should 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.
java 代码
- handle_info({'EXIT', Pid, Reason}, StateName, StateData) ->
- ..code to handle exits here..
- {next_state, StateName1, StateData1}.