From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- system/doc/design_principles/fsm.xml | 313 +++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 system/doc/design_principles/fsm.xml (limited to 'system/doc/design_principles/fsm.xml') 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 @@ + + + + +
+ + 19972009 + Ericsson AB. All Rights Reserved. + + + 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. + + + + Gen_Fsm Behaviour + + + + + fsm.xml +
+

This chapter should be read in conjunction with gen_fsm(3), + 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 the event E occurs, we + should perform the actions A and make a transition to + the 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 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:

+ + + 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.

+
+ +
+ Starting a 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, Code, []). +

start_link calls the function gen_fsm:start_link/4. + This function 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 will be locally registered + as code_lock.

+

If the name is omitted, the gen_fsm is not registered. + Instead its pid must be used. The name could 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.

+

In this case, the interface functions (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.

+
+ +

The third argument, 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.

+
+ +

The fourth argument, [], is a list of options. See + 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.

+ +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.

+
+ +
+ 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.

+
+ +
+ Timeouts +

When a correct code has been givened, the door is unlocked and + the following tuple is returned from locked/2:

+ +{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:

+ +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 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):

+ +init(Args) -> + ..., + process_flag(trap_exit, true), + ..., + {ok, StateName, StateData}. + +... + +terminate(shutdown, StateName, StateData) -> + ..code for cleaning up here.. + ok. +
+ +
+ Stand-Alone Gen_Fsms +

If the gen_fsm is not part of a supervision tree, a stop + function may 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 will + cause the gen_fsm to call + terminate(normal,StateName,StateData1) and then + terminate gracefully:

+
+
+ +
+ Handling Other Messages +

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.

+ +handle_info({'EXIT', Pid, Reason}, StateName, StateData) -> + ..code to handle exits here.. + {next_state, StateName1, StateData1}. +
+
+ -- cgit v1.2.3