From 259c8c7bde51f0b25707f0101195aff22650e952 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
When the
@@ -138,9 +142,9 @@ erlang:'!' -----> Module:StateName/3
This gathers all code for a specific state
in one function as the
When the
@@ -249,11 +253,10 @@ erlang:'!' -----> Module:StateName/3
-behaviour(gen_statem).
-export([start/0,push/0,get_count/0,stop/0]).
--export([terminate/3,code_change/4,init/1]).
+-export([terminate/3,code_change/4,init/1,callback_mode/0]).
-export([on/3,off/3]).
name() -> pushbutton_statem. % The registered server name
-callback_mode() -> state_functions.
%% API. This example uses a registered name name()
%% and does not link to the caller.
@@ -270,15 +273,14 @@ stop() ->
terminate(_Reason, _State, _Data) ->
void.
code_change(_Vsn, State, Data, _Extra) ->
- {callback_mode(),State,Data}.
+ {ok,State,Data}.
init([]) ->
- %% Set the callback mode and initial state + data.
- %% Data is used only as a counter.
+ %% Set the initial state + data. Data is used only as a counter.
State = off, Data = 0,
- {callback_mode(),State,Data}.
-
+ {ok,State,Data}.
+callback_mode() -> state_functions.
-%%% State functions
+%%% State function(s)
off({call,From}, push, Data) ->
%% Go to 'on', increment count and reply
@@ -326,18 +328,13 @@ ok
To compare styles, here follows the same example using
-init([]) ->
- %% Set the callback mode and initial state + data.
- %% Data is used only as a counter.
- State = off, Data = 0,
- {handle_event_function,State,Data}.
-
+callback_mode() -> handle_event_function.
-%%% Event handling
+%%% State function(s)
handle_event({call,From}, push, off, Data) ->
%% Go to 'on', increment count and reply
@@ -426,8 +423,8 @@ handle_event(_, _, State, Data) ->
Debug option that can be used when starting
- a gen_statem server through, for example,
- enter_loop/5 .
+ a gen_statem server through,
+ enter_loop/4-6 .
For every entry in Dbgs ,
@@ -525,12 +522,9 @@ handle_event(_, _, State, Data) ->
The callback mode is selected when starting the
- gen_statem using the return value from
- Module:init/1
- or when calling
- enter_loop/5,6,7 ,
- and with the return value from
- Module:code_change/4 .
+ gen_statem and after code change
+ using the return value from
+ Module:callback_mode/0 .
state_functions
@@ -691,7 +685,7 @@ handle_event(_, _, State, Data) ->
state function , from
Module:init/1
or by giving them to
- enter_loop/6,7 .
+ enter_loop/5,6 .
Actions are executed in the containing list order.
@@ -958,35 +952,36 @@ handle_event(_, _, State, Data) ->
-
+
Enter the gen_statem receive loop.
The same as
- enter_loop/7
- except that no
+ enter_loop/6
+ with Actions = [] except that no
server_name()
- must have been registered.
+ must have been registered. This creates an anonymous server.
-
+
Enter the gen_statem receive loop.
If Server_or_Actions is a list() ,
the same as
- enter_loop/7
+ enter_loop/6
except that no
server_name()
must have been registered and
Actions = Server_or_Actions .
+ This creates an anonymous server.
Otherwise the same as
- enter_loop/7
+ enter_loop/6
with
Server = Server_or_Actions and
Actions = [] .
@@ -995,7 +990,7 @@ handle_event(_, _, State, Data) ->
-
+
Enter the gen_statem receive loop.
@@ -1015,21 +1010,31 @@ handle_event(_, _, State, Data) ->
the gen_statem behavior provides.
- Module , Opts , and
- Server have the same meanings
- as when calling
+ Module , Opts
+ have the same meaning as when calling
start[_link]/3,4 .
+
+
+ If Server is self() an anonymous
+ server is created just as when using
+ start[_link]/3 .
+ If Server is a
+ server_name()
+ a named server is created just as when using
+ start[_link]/4 .
However, the
server_name()
name must have been registered accordingly
- before this function is called.
+ before this function is called.
+
- CallbackMode , State ,
- Data , and Actions
+ State , Data ,
+ and Actions
have the same meanings as in the return value of
Module:init/1 .
- Also, the callback module Module
- does not need to export an init/1 function.
+ Also, the callback module does not need to export a
+ Module:init/1
+ function.
The function fails if the calling process was not started by a
@@ -1252,6 +1257,54 @@ handle_event(_, _, State, Data) ->
+
+ Module:callback_mode() -> CallbackMode
+ Update the internal state during upgrade/downgrade.
+
+
+ CallbackMode =
+ callback_mode()
+
+
+
+
+ This function is called by a gen_statem
+ when it needs to find out the
+ callback mode
+ of the callback module. The value is cached by gen_statem
+ for efficiency reasons, so this function is only called
+ once after server start and after code change,
+ but before the first
+ state function
+ is called. More occasions may be added in future versions
+ of gen_statem .
+
+
+ Server start happens either when
+ Module:init/1
+ returns or when
+ enter_loop/4-6
+ is called. Code change happens when
+ Module:code_change/4
+ returns.
+
+
+ This function can use
+ erlang:throw/1
+ to return CallbackMode , just for symmetry reasons.
+ There should be no actual reason to use this.
+
+
+
+ If this function's body does not consist of solely one of two
+ possible
+ atoms
+ the callback module is doing something strange.
+
+
+
+
+
Module:code_change(OldVsn, OldState, OldData, Extra) ->
Result
@@ -1262,11 +1315,7 @@ handle_event(_, _, State, Data) ->
Vsn = term()
OldState = NewState = term()
Extra = term()
- Result = {CallbackMode,NewState,NewData} | Reason
-
- CallbackMode =
- callback_mode()
-
+ Result = {ok,NewState,NewData} | Reason
OldState = NewState =
state()
@@ -1295,21 +1344,6 @@ handle_event(_, _, State, Data) ->
Module . If no such attribute is defined, the version
is the checksum of the Beam file.
-
-
- If you would dare to change
- callback mode
- during release upgrade/downgrade, the upgrade is no problem,
- as the new code surely knows what callback mode
- it needs. However, for a downgrade this function must
- know from argument Extra that comes from the
- sasl:appup
- file what callback mode the old code did use.
- It can also be possible to figure this out
- from argument {down,Vsn} , as Vsn
- in effect defines the old callback module version.
-
-
OldState and OldData is the internal state
of the gen_statem .
@@ -1321,16 +1355,16 @@ handle_event(_, _, State, Data) ->
If successful, the function must return the updated
internal state in an
- {CallbackMode,NewState,NewData} tuple.
+ {ok,NewState,NewData} tuple.
If the function returns a failure Reason , the ongoing
upgrade fails and rolls back to the old release.
- Note that Reason can not be a 3-tuple since that
- will be regarded as a
- {CallbackMode,NewState,NewData} tuple,
+ Note that Reason can not be an {ok,_,_} tuple
+ since that will be regarded as a
+ {ok,NewState,NewData} tuple,
and that a tuple matching {ok,_}
- is an invalid failure Reason .
+ is an also invalid failure Reason .
It is recommended to use an atom as Reason since
it will be wrapped in an {error,Reason} tuple.
@@ -1344,16 +1378,14 @@ handle_event(_, _, State, Data) ->
Module:init(Args) -> Result
- Initialize process and internal state.
+
+ Optional function for initializing process and internal state.
+
Args = term()
- Result = {CallbackMode,State,Data}
- | {CallbackMode,State,Data,Actions}
+ Result = {ok,State,Data}
+ | {ok,State,Data,Actions}
| {stop,Reason} | ignore
-
- CallbackMode =
- callback_mode()
-
State = state()
Data = data()
@@ -1372,7 +1404,7 @@ handle_event(_, _, State, Data) ->
start_link/3,4
or
start/3,4 ,
- this function is called by the new process to initialize
+ this optional function is called by the new process to initialize
the implementation state and server data.
@@ -1381,11 +1413,8 @@ handle_event(_, _, State, Data) ->
If the initialization is successful, the function is to
- return {CallbackMode,State,Data} or
- {CallbackMode,State,Data,Actions} .
- CallbackMode selects the
- callback mode
- of the gen_statem .
+ return {ok,State,Data} or
+ {ok,State,Data,Actions} .
State is the initial
state()
and Data the initial server
@@ -1408,6 +1437,16 @@ handle_event(_, _, State, Data) ->
erlang:throw/1
to return Result .
+
+
+ This callback is optional, so a callback module does not need
+ to export it, but most do. If this function is not exported,
+ the gen_statem should be started through
+ proc_lib
+ and
+ enter_loop/4-6 .
+
+
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index aea623851a..1351997bc1 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -52,7 +52,7 @@
Event-Driven State Machines
- Established Automata theory does not deal much with
+ Established Automata Theory does not deal much with
how a state transition is triggered,
but assumes that the output is a function
of the input (and the state) and that they are
@@ -226,11 +226,10 @@ handle_event(EventType, EventContent, State, Data) ->
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock).
--define(CALLBACK_MODE, state_functions).
-export([start_link/1]).
-export([button/1]).
--export([init/1,terminate/3,code_change/4]).
+-export([init/1,callback_mode/0,terminate/3,code_change/4]).
-export([locked/3,open/3]).
start_link(Code) ->
@@ -242,7 +241,10 @@ button(Digit) ->
init(Code) ->
do_lock(),
Data = #{code => Code, remaining => Code},
- {?CALLBACK_MODE,locked,Data}.
+ {ok,locked,Data}.
+
+callback_mode() ->
+ state_functions.
locked(
cast, {button,Digit},
@@ -273,7 +275,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {?CALLBACK_MODE,State,Data}.
+ {ok,State,Data}.
]]>
The code is explained in the next sections.
@@ -343,14 +345,8 @@ start_link(Code) ->
If name registration succeeds, the new
do_lock(),
Data = #{code => Code, remaining => Code},
- {?CALLBACK_MODE,locked,Data}.
+ {ok,locked,Data}.
]]>
Function
+ state_functions.
+ ]]>
+
+ Function
+
+ handle_event_function.
handle_event(cast, {button,Digit}, State, #{code := Code} = Data) ->
case State of
@@ -962,16 +973,13 @@ do_unlock() ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(?CALLBACK_MODE, locked, Data).
+ enter(ok, locked, Data).
-...
+callback_mode() ->
+ state_functions.
locked(internal, enter, _Data) ->
do_lock(),
@@ -1027,11 +1035,10 @@ enter(Tag, State, Data) ->
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock_2).
--define(CALLBACK_MODE, state_functions).
-export([start_link/1,stop/0]).
-export([button/1,code_length/0]).
--export([init/1,terminate/3,code_change/4]).
+-export([init/1,callback_mode/0,terminate/3,code_change/4]).
-export([locked/3,open/3]).
start_link(Code) ->
@@ -1047,7 +1054,10 @@ code_length() ->
init(Code) ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(?CALLBACK_MODE, locked, Data).
+ enter(ok, locked, Data).
+
+callback_mode() ->
+ state_functions.
locked(internal, enter, #{code := Code} = Data) ->
do_lock(),
@@ -1091,7 +1101,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {?CALLBACK_MODE,State,Data}.
+ {ok,State,Data}.
]]>
@@ -1105,13 +1115,12 @@ code_change(_Vsn, State, Data, _Extra) ->
entry actions, so this example first branches depending on state:
+ handle_event_function.
%% State: locked
handle_event(internal, enter, locked, #{code := Code} = Data) ->
@@ -1273,11 +1282,10 @@ format_status(Opt, [_PDict,State,Data]) ->
-module(code_lock).
-behaviour(gen_statem).
-define(NAME, code_lock_3).
--define(CALLBACK_MODE, handle_event_function).
-export([start_link/2,stop/0]).
-export([button/1,code_length/0,set_lock_button/1]).
--export([init/1,terminate/3,code_change/4,format_status/2]).
+-export([init/1,callback_mode/0,terminate/3,code_change/4,format_status/2]).
-export([handle_event/4]).
start_link(Code, LockButton) ->
@@ -1296,7 +1304,10 @@ set_lock_button(LockButton) ->
init({Code,LockButton}) ->
process_flag(trap_exit, true),
Data = #{code => Code, remaining => undefined, timer => undefined},
- enter(?CALLBACK_MODE, {locked,LockButton}, Data, []).
+ enter(ok, {locked,LockButton}, Data, []).
+
+callback_mode() ->
+ handle_event_function.
handle_event(
{call,From}, {set_lock_button,NewLockButton},
@@ -1366,7 +1377,7 @@ terminate(_Reason, State, _Data) ->
State =/= locked andalso do_lock(),
ok.
code_change(_Vsn, State, Data, _Extra) ->
- {?CALLBACK_MODE,State,Data}.
+ {ok,State,Data}.
format_status(Opt, [_PDict,State,Data]) ->
StateData =
{State,
--
cgit v1.2.3