@@ -1826,7 +1846,7 @@ handle_event(_, _, State, Data) ->
for efficiency reasons, so this function is only called
once after server start and after code change,
but before the first
- state callback
+ state callback
in the current code version is called.
More occasions may be added in future versions
of gen_statem.
@@ -1883,7 +1903,7 @@ handle_event(_, _, State, Data) ->
This callback is optional, so callback modules need not export it.
If a release upgrade/downgrade with
- Change={advanced,Extra}
+ Change = {advanced,Extra}
specified in the .appup file is made
when code_change/4 is not implemented
the process will crash with exit reason undef.
@@ -1893,7 +1913,7 @@ handle_event(_, _, State, Data) ->
This function is called by a gen_statem when it is to
update its internal state during a release upgrade/downgrade,
that is, when the instruction {update,Module,Change,...},
- where Change={advanced,Extra}, is specified in the
+ where Change = {advanced,Extra}, is specified in the
appup
file. For more information, see
OTP Design Principles.
@@ -1933,13 +1953,13 @@ handle_event(_, _, State, Data) ->
Also note when upgrading a gen_statem,
this function and hence
- the Change={advanced,Extra} parameter in the
+ the Change = {advanced,Extra} parameter in the
appup file
is not only needed to update the internal state
or to act on the Extra argument.
It is also needed if an upgrade or downgrade should change
callback mode,
- or else the callback mode after the code change
+ or else the callback mode after the code change
will not be honoured,
most probably causing a server crash.
@@ -2148,7 +2168,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).
{call,From},
the caller waits for a reply. The reply can be sent
from this or from any other
- state callback
+ state callback
by returning with {reply,From,Reply} in
Actions, in
Replies,
@@ -2173,9 +2193,9 @@ init(Args) -> erlang:error(not_implemented, [Args]).
When the gen_statem runs with
- state enter calls,
+ state enter calls,
these functions are also called with arguments
- (enter, OldState, ...) whenever the state changes.
+ (enter, OldState, ...) during every state change.
In this case there are some restrictions on the
actions
that may be returned:
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index faa43fbc1e..24b268cd38 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -330,6 +330,7 @@
%% Type validation functions
+%% - return true if the value is of the type, false otherwise
-compile(
{inline,
[callback_mode/1, state_enter/1,
@@ -1277,7 +1278,7 @@ parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
end.
parse_actions_reply(
- StateCall, ?not_sys_debug, S, Actions, TransOpts,
+ StateCall, ?not_sys_debug = Debug, S, Actions, TransOpts,
From, Reply) ->
%%
case from(From) of
@@ -1287,8 +1288,7 @@ parse_actions_reply(
false ->
[error,
{bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(),
- ?not_sys_debug]
+ ?STACKTRACE(), Debug]
end;
parse_actions_reply(
StateCall, Debug, #state{name = Name, state = State} = S,
@@ -1302,12 +1302,11 @@ parse_actions_reply(
false ->
[error,
{bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(),
- Debug]
+ ?STACKTRACE(), Debug]
end.
parse_actions_next_event(
- StateCall, ?not_sys_debug, S,
+ StateCall, ?not_sys_debug = Debug, S,
Actions, TransOpts, Type, Content) ->
case event_type(Type) of
true when StateCall ->
@@ -1320,8 +1319,7 @@ parse_actions_next_event(
[error,
{bad_state_enter_action_from_state_function,
{next_event,Type,Content}},
- ?STACKTRACE(),
- ?not_sys_debug]
+ ?STACKTRACE(), Debug]
end;
parse_actions_next_event(
StateCall, Debug, #state{name = Name, state = State} = S,
@@ -1403,13 +1401,13 @@ parse_actions_timeout_add(
loop_event_done(
Parent, ?not_sys_debug,
#state{postponed = P} = S,
+%% #state{postponed = will_not_happen = P} = S,
Events, Event, NextState, NewData,
#trans_opts{
postpone = Postpone, hibernate = Hibernate,
- timeouts_r = [], next_events_r = []}) ->
+ timeouts_r = [], next_events_r = NextEventsR}) ->
%%
- %% Optimize the simple cases
- %% i.e no timer changes, no inserted events and no debug,
+ %% Optimize the simple cases i.e no debug and no timer changes,
%% by duplicate stripped down code
%%
%% Fast path
@@ -1417,14 +1415,12 @@ loop_event_done(
case Postpone of
true ->
loop_event_done_fast(
- Parent, Hibernate,
- S,
- Events, [Event|P], NextState, NewData);
+ Parent, Hibernate, S,
+ Events, [Event|P], NextState, NewData, NextEventsR);
false ->
loop_event_done_fast(
- Parent, Hibernate,
- S,
- Events, P, NextState, NewData)
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR)
end;
loop_event_done(
Parent, Debug_0,
@@ -1456,26 +1452,23 @@ loop_event_done(
{S#state.name,State},
{consume,Event_0,NextState})|P_0]
end,
- {Events_2,P_2,Timers_2} =
- %% Move all postponed events to queue,
- %% cancel the event timer,
- %% and cancel the state timeout if the state changes
- if
- NextState =:= State ->
- {Events_0,P_1,
+ {Events_2,P_2,
+ Timers_2} =
+ %% Cancel the event timeout
+ if
+ NextState =:= State ->
+ {Events_0,P_1,
cancel_timer_by_type(
timeout, {TimerTypes_0,CancelTimers_0})};
- true ->
- {lists:reverse(P_1, Events_0),
- [],
- cancel_timer_by_type(
- state_timeout,
+ true ->
+ %% Move all postponed events to queue
+ %% and cancel the state timeout
+ {lists:reverse(P_1, Events_0),[],
+ cancel_timer_by_type(
+ state_timeout,
cancel_timer_by_type(
timeout, {TimerTypes_0,CancelTimers_0}))}
- %% The state timer is removed from TimerTypes
- %% but remains in TimerRefs until we get
- %% the cancel_timer msg
- end,
+ end,
{TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} =
%% Stop and start timers
parse_timers(TimerRefs_0, Timers_2, TimeoutsR),
@@ -1495,114 +1488,144 @@ loop_event_done(
hibernate = Hibernate},
lists:reverse(Events_4R)).
+loop_event_done(Parent, Debug, S, Q) ->
+%% io:format(
+%% "loop_event_done:~n"
+%% " state = ~p, data = ~p, postponed = ~p~n "
+%% " timer_refs = ~p, timer_types = ~p, cancel_timers = ~p.~n"
+%% " Q = ~p.~n",
+%% [S#state.state,S#state.data,S#state.postponed,
+%% S#state.timer_refs,S#state.timer_types,S#state.cancel_timers,
+%% Q]),
+ case Q of
+ [] ->
+ %% Get a new event
+ loop(Parent, Debug, S);
+ [{Type,Content}|Events] ->
+ %% Loop until out of enqueued events
+ loop_event(Parent, Debug, S, Events, Type, Content)
+ end.
+
+
%% Fast path
%%
+%% Cancel event timer and state timer only if running
loop_event_done_fast(
Parent, Hibernate,
#state{
state = NextState,
- timer_types = #{timeout := _} = TimerTypes,
+ timer_types = TimerTypes,
cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% Same state, event timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers}));
-loop_event_done_fast(
- Parent, Hibernate,
- #state{state = NextState} = S,
- Events, P, NextState, NewData) ->
- %%
+ Events, P, NextState, NewData, NextEventsR) ->
%% Same state
- %%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- data = NewData,
- postponed = P,
- hibernate = Hibernate},
- Events);
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- timer_types = #{timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% State change, event timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
+ case TimerTypes of
+ #{timeout := _} ->
+ %% Event timeout active
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ cancel_timer_by_type(
+ timeout, {TimerTypes,CancelTimers}));
+ _ ->
+ %% No event timeout active
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ {TimerTypes,CancelTimers})
+ end;
loop_event_done_fast(
Parent, Hibernate,
#state{
- timer_types = #{state_timeout := _} = TimerTypes,
+ timer_types = TimerTypes,
cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% State change, state timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
+ Events, P, NextState, NewData, NextEventsR) ->
+ %% State change
+ case TimerTypes of
+ #{timeout := _} ->
+ %% Event timeout active
+ %% - cancel state_timeout too since it is faster than inspecting
+ loop_event_done_fast(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ cancel_timer_by_type(
+ state_timeout,
+ cancel_timer_by_type(
+ timeout, {TimerTypes,CancelTimers})));
+ #{state_timeout := _} ->
+ %% State_timeout active but not event timeout
+ loop_event_done_fast(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ cancel_timer_by_type(
+ state_timeout, {TimerTypes,CancelTimers}));
+ _ ->
+ %% No event nor state_timeout active
+ loop_event_done_fast(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ {TimerTypes,CancelTimers})
+ end.
+%%
+%% Retry postponed events
loop_event_done_fast(
- Parent, Hibernate,
- #state{} = S,
- Events, P, NextState, NewData) ->
- %%
- %% State change, no timeout to automatically cancel
- %%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- state = NextState,
- data = NewData,
- postponed = [],
- hibernate = Hibernate},
- lists:reverse(P, Events)).
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR, TimerTypes_CancelTimers) ->
+ case P of
+ %% Handle 0..2 postponed events without list reversal since
+ %% that will move out all live registers and back again
+ [] ->
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ Events, [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers);
+ [E] ->
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ [E|Events], [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers);
+ [E1,E2] ->
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ [E2,E1|Events], [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers);
+ _ ->
+ %% A bit slower path
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ lists:reverse(P, Events), [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers)
+ end.
%%
%% Fast path
%%
-loop_event_done_fast(
+loop_event_done_fast_2(
Parent, Hibernate, S,
- Events, P, NextState, NewData,
+ Events, P, NextState, NewData, NextEventsR,
{TimerTypes,CancelTimers}) ->
%%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- state = NextState,
- data = NewData,
- postponed = P,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers,
- hibernate = Hibernate},
- Events).
-
-loop_event_done(Parent, Debug, S, Q) ->
- case Q of
+ NewS =
+ S#state{
+ state = NextState,
+ data = NewData,
+ postponed = P,
+ timer_types = TimerTypes,
+ cancel_timers = CancelTimers,
+ hibernate = Hibernate},
+ case NextEventsR of
+ %% Handle 0..2 next events without list reversal since
+ %% that will move out all live registers and back again
[] ->
- %% Get a new event
- loop(Parent, Debug, S);
- [{Type,Content}|Events] ->
- %% Loop until out of enqueued events
- loop_event(Parent, Debug, S, Events, Type, Content)
+ loop_event_done(Parent, ?not_sys_debug, NewS, Events);
+ [E] ->
+ loop_event_done(Parent, ?not_sys_debug, NewS, [E|Events]);
+ [E2,E1] ->
+ loop_event_done(Parent, ?not_sys_debug, NewS, [E1,E2|Events]);
+ _ ->
+ %% A bit slower path
+ loop_event_done(
+ Parent, ?not_sys_debug, NewS, lists:reverse(NextEventsR, Events))
end.
-
%%---------------------------------------------------------------------------
%% Server loop helpers
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 053233df9b..017939fdd6 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -121,7 +121,8 @@ end_per_testcase(_CaseName, Config) ->
start1(Config) ->
%%OldFl = process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid0} =
+ gen_statem:start_link(?MODULE, start_arg(Config, []), [{debug,[trace]}]),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stop_it(Pid0),
@@ -135,7 +136,8 @@ start1(Config) ->
%% anonymous w. shutdown
start2(Config) ->
%% Dont link when shutdown
- {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid0} =
+ gen_statem:start(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stopped = gen_statem:call(Pid0, {stop,shutdown}),
@@ -641,51 +643,72 @@ state_enter(_Config) ->
end,
start =>
fun (enter, Prev, N) ->
- Self ! {enter,start,Prev,N},
+ Self ! {N,enter,start,Prev},
{keep_state,N + 1};
(internal, Prev, N) ->
- Self ! {internal,start,Prev,N},
+ Self ! {N,internal,start,Prev},
{keep_state,N + 1};
+ (timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {timeout,M}}};
({call,From}, repeat, N) ->
{repeat_state,N + 1,
- [{reply,From,{repeat,start,N}}]};
+ [{reply,From,{N,repeat,start}}]};
({call,From}, echo, N) ->
{next_state,wait,N + 1,
- {reply,From,{echo,start,N}}};
+ [{reply,From,{N,echo,start}},{timeout,0,N}]};
({call,From}, {stop,Reason}, N) ->
{stop_and_reply,Reason,
- [{reply,From,{stop,N}}],N + 1}
+ [{reply,From,{N,stop}}],N + 1}
end,
wait =>
fun (enter, Prev, N) when N < 5 ->
{repeat_state,N + 1,
- {reply,{Self,N},{enter,Prev}}};
+ [{reply,{Self,N},{enter,Prev}},
+ {timeout,0,N},
+ {state_timeout,0,N}]};
(enter, Prev, N) ->
- Self ! {enter,wait,Prev,N},
- {keep_state,N + 1};
+ Self ! {N,enter,wait,Prev},
+ {keep_state,N + 1,
+ [{timeout,0,N},
+ {state_timeout,0,N}]};
+ (timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {timeout,M}}};
+ (state_timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {state_timeout,M}}};
({call,From}, repeat, N) ->
{repeat_state_and_data,
- [{reply,From,{repeat,wait,N}}]};
+ [{reply,From,{N,repeat,wait}},
+ {timeout,0,N}]};
({call,From}, echo, N) ->
{next_state,start,N + 1,
[{next_event,internal,wait},
- {reply,From,{echo,wait,N}}]}
+ {reply,From,{N,echo,wait}}]}
end},
{ok,STM} =
gen_statem:start_link(
- ?MODULE, {map_statem,Machine,[state_enter]}, []),
-
- [{enter,start,start,1}] = flush(),
- {echo,start,2} = gen_statem:call(STM, echo),
- [{3,{enter,start}},{4,{enter,start}},{enter,wait,start,5}] = flush(),
- {wait,[6|_]} = sys:get_state(STM),
- {repeat,wait,6} = gen_statem:call(STM, repeat),
- [{enter,wait,wait,6}] = flush(),
- {echo,wait,7} = gen_statem:call(STM, echo),
- [{enter,start,wait,8},{internal,start,wait,9}] = flush(),
- {repeat,start,10} = gen_statem:call(STM, repeat),
- [{enter,start,start,11}] = flush(),
- {stop,12} = gen_statem:call(STM, {stop,bye}),
+ ?MODULE, {map_statem,Machine,[state_enter]}, [{debug,[trace]}]),
+
+ [{1,enter,start,start}] = flush(),
+ {2,echo,start} = gen_statem:call(STM, echo),
+ [{3,{enter,start}},
+ {4,{enter,start}},
+ {5,enter,wait,start},
+ {6,{timeout,5}},
+ {7,{state_timeout,5}}] = flush(),
+ {wait,[8|_]} = sys:get_state(STM),
+ {8,repeat,wait} = gen_statem:call(STM, repeat),
+ [{8,enter,wait,wait},
+ {9,{timeout,8}},
+ {10,{state_timeout,8}}] = flush(),
+ {11,echo,wait} = gen_statem:call(STM, echo),
+ [{12,enter,start,wait},
+ {13,internal,start,wait}] = flush(),
+ {14,repeat,start} = gen_statem:call(STM, repeat),
+ [{15,enter,start,start}] = flush(),
+ {16,stop} = gen_statem:call(STM, {stop,bye}),
[{'EXIT',STM,bye}] = flush(),
{noproc,_} =
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 98fd1fd69d..29e19163a5 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -44,27 +44,39 @@
Event-Driven State Machines
Established Automata Theory does not deal much with
- how a state transition is triggered,
+ how a state transition is triggered,
but assumes that the output is a function
of the input (and the state) and that they are
some kind of values.
For an Event-Driven State Machine, the input is an event
- that triggers a state transition and the output
- is actions executed during the state transition.
+ that triggers a state transition and the output
+ is actions executed during the state transition.
It can analogously to the mathematical model of a
- Finite-State Machine be described as
+ Finite State Machine be described as
a set of relations of the following form:
State(S) x Event(E) -> Actions(A), State(S')
- These relations are interpreted as follows:
+
+ These relations are interpreted as follows:
if we are in state S and event E occurs, we
are to perform actions A and make a transition to
state S'. Notice that S' can be equal to S
and that A can be empty.
+
+ In gen_statem we define
+ a state change as a state transition
+ in which the new state S' is different from
+ the current state S, where "different" means
+ Erlang's strict inequality: =/=
+ also know as "does not match".
+ During a state changes,
+ gen_statem does more things
+ than during other state transitions.
+
As A and S' depend only on
S and E, the kind of state machine described
@@ -95,8 +107,8 @@ State(S) x Event(E) -> Actions(A), State(S')
-
Co-located callback code for each state,
- regardless of
- Event Type
+ for all
+ Event Types
(such as call, cast and info)
-
@@ -114,13 +126,13 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- State Enter Calls
+ State Enter Calls
(callback on state entry co-located with the rest
of each state's callback code)
-
- Easy-to-use timeouts
+ Easy-to-use time-outs
(State Time-Outs,
Event Time-Outs
and
@@ -152,11 +164,11 @@ State(S) x Event(E) -> Actions(A), State(S')
Callback Module
- The callback module contains functions that implement
+ The callback module contains functions that implement
the state machine.
When an event occurs,
the gen_statem behaviour engine
- calls a function in the callback module with the event,
+ calls a function in the callback module with the event,
current state and server data.
This function performs the actions for this event,
and returns the new state and server data
@@ -166,7 +178,7 @@ State(S) x Event(E) -> Actions(A), State(S')
The behaviour engine holds the state machine state,
server data, timer references, a queue of posponed messages
and other metadata. It receives all process messages,
- handles the system messages, and calls the callback module
+ handles the system messages, and calls the callback module
with machine specific events.
@@ -177,7 +189,7 @@ State(S) x Event(E) -> Actions(A), State(S')
Callback Modes
- The gen_statem behavior supports two callback modes:
+ The gen_statem behavior supports two callback modes:
@@ -202,31 +214,33 @@ State(S) x Event(E) -> Actions(A), State(S')
- The callback mode is selected at server start
+ The callback mode is selected at server start
and may be changed with a code upgrade/downgrade.
See the section
- Event Handler
+ State Callback
that describes the event handling callback function(s).
- The callback mode is selected by implementing
+ The callback mode is selected by implementing
a mandatory callback function
Module:callback_mode()
- that returns one of the callback modes.
+ that returns one of the callback modes.
The
Module:callback_mode()
- function may also return a list containing the callback mode
+ function may also return a list containing the callback mode
and the atom state_enter in which case
- State Enter Calls
- are activated for the callback mode.
+
+ state enter calls
+
+ are activated for the callback mode.
@@ -237,11 +251,11 @@ State(S) x Event(E) -> Actions(A), State(S')
it is the one most like gen_fsm.
But if you do not want the restriction that the state
must be an atom, or if you do not want to write
- one event handler function per state; please read on...
+ one state callback function per state; please read on...
The two
- Callback Modes
+ callback modes
give different possibilities and restrictions,
with one common goal:
to handle all possible combinations of events and states.
@@ -257,7 +271,7 @@ State(S) x Event(E) -> Actions(A), State(S')
With state_functions, you are restricted to use
atom-only states, and the gen_statem engine
branches depending on state name for you.
- This encourages the callback module to co-locate
+ This encourages the callback module to co-locate
the implementation of all event actions particular
to one state in the same place in the code,
hence to focus on one state at the time.
@@ -302,11 +316,12 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- Event Handler
+
+ State Callback
- Which callback function that handles an event
- depends on the callback mode:
+ The state callback is the callback function
+ that handles an event in the current state,
+ and which function that is depends on the callback mode:
state_functions
@@ -329,7 +344,9 @@ State(S) x Event(E) -> Actions(A), State(S')
See section
- One Event Handler
+
+ One State Callback
+
for an example.
@@ -338,15 +355,17 @@ State(S) x Event(E) -> Actions(A), State(S')
The state is either the name of the function itself or an argument to it.
The other arguments are the EventType described in section
Event Types,
- the event dependent EventContent, and the current server Data.
+ the event dependent EventContent,
+ and the current server Data.
- State enter calls are also handled by the event handler and have
- slightly different arguments. See the section
+ State enter calls are also handled by the event handler
+ and have slightly different arguments. See section
State Enter Calls.
- The event handler return values are defined in the description of
+ The state callback return values
+ are defined in the description of
Module:StateName/3
@@ -361,24 +380,29 @@ State(S) x Event(E) -> Actions(A), State(S')
-
Set next state and update the server data.
- If the Actions field is used, execute state transition actions.
- An empty Actions list is equivalent to not returning the field.
+ If the Actions field is used,
+ execute transition actions.
+ An empty Actions list is equivalent to
+ not returning the field.
See section
-
- State Transition Actions
+
+ Transition Actions
for a list of possible
- state transition actions.
+ transition actions.
- If NextState =/= State the state machine changes
- to a new state. A
+ If NextState =/= State this is a state change
+ so the extra things gen_statem does are: the event queue
+ is restarted from the oldest
+ postponed event,
+ any current
+ state time-out
+ is cancelled, and a
state enter call
- is performed if enabled and all
- postponed events
- are retried.
+ is performed, if enabled.
@@ -388,7 +412,7 @@ State(S) x Event(E) -> Actions(A), State(S')
-
Same as the next_state values with
- NextState =:= State, that is, no state change.
+ NextState =:= State, that is, no state change.
@@ -414,9 +438,16 @@ State(S) x Event(E) -> Actions(A), State(S')
State Enter Calls
- are enabled, repeat the state enter call
+ are enabled, repeat the state enter call
as if this state was entered again.
+
+ If these return values are used from a
+ state enter call the OldState does not change,
+ but if used from an event handling state callback
+ the new state enter call's OldState
+ will be the current state.
+
{stop, Reason, NewData}
@@ -435,7 +466,10 @@ State(S) x Event(E) -> Actions(A), State(S')
-
Same as the stop values, but first execute the given
- state transition actions that may only be reply actions.
+
+ transition actions
+
+ that may only be reply actions.
@@ -449,8 +483,8 @@ State(S) x Event(E) -> Actions(A), State(S')
Module:init(Args)
callback function is called before any
- Event Handler
- is called. This function behaves like an event handler
+ state callback
+ is called. This function behaves like an state callback
function, but gets its only argument Args from
the gen_statem
@@ -474,8 +508,8 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- State Transition Actions
+
+ Transition Actions
In the first section
@@ -483,13 +517,13 @@ State(S) x Event(E) -> Actions(A), State(S')
actions were mentioned as a part of
the general state machine model. These general actions
- are implemented with the code that callback module
+ are implemented with the code that callback module
gen_statem executes in an event-handling
callback function before returning
to the gen_statem engine.
- There are more specific state-transition actions
+ There are more specific transition actions
that a callback function can command the gen_statem
engine to do after the callback function return.
These are commanded by returning a list of
@@ -500,7 +534,7 @@ State(S) x Event(E) -> Actions(A), State(S')
from the
callback function.
- These are the possible state transition actions:
+ These are the possible transition actions:
@@ -596,13 +630,13 @@ State(S) x Event(E) -> Actions(A), State(S')
Event Types
Events are categorized in different
- event types.
+ event types.
Events of all types are for a given state
handled in the same callback function, and that function gets
EventType and EventContent as arguments.
- The following is a complete list of event types and where
+ The following is a complete list of event types and where
they come from:
@@ -624,7 +658,7 @@ State(S) x Event(E) -> Actions(A), State(S')
Generated by
gen_statem:call,
where From is the reply address to use
- when replying either through the state transition action
+ when replying either through the transition action
{reply,From,Msg} or by calling
gen_statem:reply.
@@ -643,7 +677,7 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- Generated by state transition action
+ Generated by transition action
{state_timeout,Time,EventContent}
@@ -655,7 +689,7 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- Generated by state transition action
+ Generated by transition action
{{timeout,Name},Time,EventContent}
@@ -667,7 +701,7 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- Generated by state transition action
+ Generated by transition action
{timeout,Time,EventContent}
@@ -680,10 +714,10 @@ State(S) x Event(E) -> Actions(A), State(S')
-
- Generated by state transition
+ Generated by state transition
action
{next_event,internal,EventContent}.
- All event types above can also be generated using
+ All event types above can also be generated using
{next_event,EventType,EventContent}.
@@ -696,14 +730,14 @@ State(S) x Event(E) -> Actions(A), State(S')
State Enter Calls
The gen_statem behavior can if this is enabled,
- regardless of callback mode,
+ regardless of callback mode,
automatically
call the state callback
with special arguments whenever the state changes
so you can write state enter actions
- near the rest of the state transition rules.
+ near the rest of the state transition rules.
It typically looks like this:
@@ -714,33 +748,35 @@ StateName(EventType, EventContent, Data) ->
... code for actions here ...
{next_state, NewStateName, NewData}.
- Since the state enter call is not an event there are restrictions
+ Since the state enter call is not an event there are restrictions
on the allowed return value and
State Transition Actions.
You may not change the state,
postpone
this non-event, or
- insert events.
+ insert any events.
- The first state that is entered will get a state enter call
+ The first state that is entered
+ will get a state enter call
with OldState equal to the current state.
- You may repeat the state enter call using the {repeat_state,...}
+ You may repeat the state enter call
+ using the {repeat_state,...}
return value from the
- Event Handler.
+ state callback.
In this case OldState will also be equal to the current state.
Depending on how your state machine is specified,
- this can be a very useful feature,
- but it forces you to handle the state enter calls in all states.
+ this can be a very useful feature, but it forces you to handle
+ the state enter calls in all states.
See also the
State Enter Actions
- chapter.
+ section.
@@ -765,7 +801,7 @@ StateName(EventType, EventContent, Data) ->
This code lock state machine can be implemented using
- gen_statem with the following callback module:
+ gen_statem with the following callback module:
-
The second argument, ?MODULE, is the name of
- the callback module, that is, the module where the callback
+ the callback module, that is,
+ the module where the callback
functions are located, which is this module.
@@ -935,7 +972,7 @@ init(Code) ->
Module:callback_mode/0
selects the
CallbackMode
- for the callback module, in this case
+ for the callback module, in this case
state_functions.
That is, each state has got its own handler function:
@@ -1051,11 +1088,11 @@ open(state_timeout, lock, Data) ->
]]>
The timer for a state time-out is automatically cancelled
- when the state machine changes states. You can restart
- a state time-out by setting it to a new time, which cancels
- the running timer and starts a new. This implies that
- you can cancel a state time-out by restarting it with
- time infinity.
+ when the state machine does a state change.
+ You can restart a state time-out by setting it to a new time,
+ which cancels the running timer and starts a new.
+ This implies that you can cancel a state time-out
+ by restarting it with time infinity.
@@ -1137,7 +1174,7 @@ open(...) -> ... ;
care about what it is.
- If the common event handler needs to know the current state
+ If the common state callback needs to know the current state
a function handle_common/4 can be used instead:
... ;
-
- One Event Handler
+
+ One State Callback
If
- Callback Mode
+ callback mode
handle_event_function is used,
all events are handled in
@@ -1289,7 +1326,10 @@ stop() ->
You get either an event or a time-out, but not both.
- It is ordered by the state transition action
+ It is ordered by the
+
+ transition action
+
{timeout,Time,EventContent}, or just an integer Time,
even without the enclosing actions list
(the latter is a form inherited from gen_fsm.
@@ -1315,7 +1355,7 @@ locked(
]]>
Whenever we receive a button event we start an event time-out
- of 30 seconds, and if we get an event type timeout
+ of 30 seconds, and if we get an event type of timeout
we reset the remaining code sequence.
@@ -1327,7 +1367,7 @@ locked(
Note that an event time-out does not work well with
- when you have for example a status call as in
+ when you have for example a status call as in section
All State Events,
or handle unknown events, since all kinds of events
will cancel the event time-out.
@@ -1383,14 +1423,14 @@ open(cast, {button,_}, Data) ->
]]>
Specific generic time-outs can just as
- State Time-Outs
+ state time-outs
be restarted or cancelled
by setting it to a new time or infinity.
- In this particular case we do not need to cancel the timeout
- since the timeout event is the only possible reason to
- change the state from open to locked.
+ In this particular case we do not need to cancel the time-out
+ since the time-out event is the only possible reason to
+ do a state change from open to locked.
Instead of bothering with when to cancel a time-out,
@@ -1442,7 +1482,7 @@ open(cast, {button,_}, Data) ->
]]>
Removing the timer key from the map when we
- change to state locked is not strictly
+ do a state change to locked is not strictly
necessary since we can only get into state open
with an updated timer map value. But it can be nice
to not have outdated values in the state Data!
@@ -1474,13 +1514,13 @@ open(cast, {button,_}, Data) ->
If you want to ignore a particular event in the current state
and handle it in a future state, you can postpone the event.
- A postponed event is retried after the state has
- changed, that is, OldState =/= NewState.
+ A postponed event is retried after a state change,
+ that is, OldState =/= NewState.
- Postponing is ordered by the state transition
-
- State Transition Action
+ Postponing is ordered by the
+
+ transition action
postpone.
@@ -1496,7 +1536,8 @@ open(cast, {button,_}, Data) ->
...
]]>
- Since a postponed event is only retried after a state change,
+ Since a postponed event is only retried
+ after a state change,
you have to think about where to keep a state data item.
You can keep it in the server Data
or in the State itself,
@@ -1505,7 +1546,7 @@ open(cast, {button,_}, Data) ->
(see section
Complex State)
with
- Callback Mode
+ callback mode
handle_event_function.
If a change in the value changes the set of events that is handled,
then the value should be kept in the State.
@@ -1606,17 +1647,17 @@ do_unlock() ->
sys
compatible behaviors must respond to system messages and therefore
do that in their engine receive loop,
- passing non-system messages to the callback module.
+ passing non-system messages to the callback module.
The
-
- State Transition Action
+
+ transition action
postpone is designed to model
selective receives. A selective receive implicitly postpones
any not received events, but the postpone
- state transition action explicitly postpones one received event.
+ transition action explicitly postpones one received event.
Both mechanisms have the same theoretical
@@ -1638,14 +1679,17 @@ do_unlock() ->
(described in the next section), especially if just
one or a few states has got state enter actions,
this is a perfect use case for the built in
- State Enter Calls.
+ state enter calls.
You return a list containing state_enter from your
- callback_mode/0
+
+ callback_mode/0
+
function and the gen_statem engine will call your
- state callback once with the arguments
- (enter, OldState, ...) whenever the state changes.
+ state callback once with an event
+ (enter, OldState, ...)
+ whenever it does a state change.
Then you just need to handle these event-like calls in all states.
It can sometimes be beneficial to be able to generate events
to your own state machine.
This can be done with the
-
- State Transition Action
+
+ transition action
{next_event,EventType,EventContent}.
@@ -1731,11 +1775,9 @@ open(state_timeout, lock, Data) ->
A variant of this is to use a
-
- Complex State
-
+ complex state
with
- One Event Handler.
+ one state callback.
The state is then modeled with for example a tuple
{MainFSMState,SubFSMState}.
@@ -1795,7 +1837,7 @@ open(internal, {button,_}, Data) ->
Example Revisited
This section includes the example after most of the mentioned
- modifications and some more using state enter calls,
+ modifications and some more using state enter calls,
which deserves a new state diagram:
+
+
+
+ Time-outs
+
+ Time-outs in gen_statem are started from a
+
+ transition action
+
+ during a state transition that is when exiting from the
+ state callback.
+
+
+ There are 3 types of time-outs in gen_statem:
+
+
+
+
+ state_timeout
+
+
+ -
+ There is one
+ State Time-Out
+ that is automatically cancelled by a state change.
+
+
+
+ {timeout, Name}
+
+
+ -
+ There are any number of
+ Generic Time-Outs
+ differing by their Name.
+ They have no automatic cancelling.
+
+
+
+ timeout
+
+
+ -
+ There is one
+ Event Time-Out
+ that is automatically cancelled by any event.
+ Note that
+ postponed
+ and
+ inserted
+ events cancel this timeout just as external events.
+
+
+
+ When a time-out is started any running time-out with the same tag,
+ state_timeout, {timeout, Name} or timeout,
+ is cancelled, that is the time-out is restarted with the new time.
+
+
+ All time-outs has got an EventContent that is part of the
+
+ transition action
+
+ that starts the time-out.
+ Different EventContents does not create different time-outs.
+ The EventContent is delivered to the
+ state callback
+ when the time-out expires.
+
+
+
+ Cancelling a Time-Out
+
+ If a time-out is started with the time infinity it will
+ never time out, in fact it will not even be started, and any
+ running time-out with the same tag will be cancelled.
+ The EventContent will in this case be ignored,
+ so why not set it to undefined.
+
+
+
+
+ Time-Out Zero
+
+ If a time-out is started with the time 0 it will
+ actually not be started. Instead the time-out event will
+ immediately be inserted to be processed after any events
+ already enqueued, and before any not yet received external events.
+ Note that some time-outs are automatically cancelled
+ so if you for example combine
+ postponing
+ an event in a state change with starting an
+ event time-out
+ with time 0 there will be no timeout event inserted
+ since the event time-out is cancelled by the postponed
+ event that is delivered due to the state change.
+
+
+
+
+