aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml305
-rw-r--r--lib/stdlib/src/gen_statem.erl582
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl6
-rw-r--r--lib/tools/emacs/erlang-skels.el12
-rw-r--r--system/doc/design_principles/statem.xml194
5 files changed, 639 insertions, 460 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 944e9ab13b..aa34f53d29 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -54,7 +54,8 @@
<p>
This is a new behavior in Erlang/OTP 19.0.
It has been thoroughly reviewed, is stable enough
- to be used by at least two heavy OTP applications, and is here to stay.
+ to be used by at least two heavy OTP applications,
+ and is here to stay.
Depending on user feedback, we do not expect
but can find it necessary to make minor
not backward compatible changes into Erlang/OTP 20.0.
@@ -70,7 +71,7 @@
<item>The state can be any term.</item>
<item>Events can be postponed.</item>
<item>Events can be self-generated.</item>
- <item>Automatic state entry events can be generated.</item>
+ <item>Automatic state enter code can be called.</item>
<item>A reply can be sent from a later state.</item>
<item>There can be multiple <c>sys</c> traceable replies.</item>
</list>
@@ -195,10 +196,14 @@ erlang:'!' -----> Module:StateName/3
to force processing an inserted event before others.
</p>
<p>
- The <c>gen_statem</c> engine can automatically insert
- a special event whenever a new state is entered; see
- <seealso marker="#type-state_entry_mode"><c>state_entry_mode()</c></seealso>.
- This makes it easy to handle code common to all state entries.
+ The <c>gen_statem</c> engine can automatically
+ make a specialized call to the
+ <seealso marker="#state_function">state function</seealso>
+ whenever a new state is entered; see
+ <seealso marker="#type-state_enter"><c>state_enter()</c></seealso>.
+ This is for writing code common to all state entries.
+ Another way to do it is to insert events at state transitions,
+ but you have to do so everywhere it is needed.
</p>
<note>
<p>If you in <c>gen_statem</c>, for example, postpone
@@ -527,6 +532,20 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="callback_mode_result"/>
+ <desc>
+ <p>
+ This is the return type from
+ <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
+ and selects
+ <seealso marker="#type-callback_mode">callback mode</seealso>
+ and whether to do
+ <seealso marker="#type-state_enter">state enter calls</seealso>,
+ or not.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="callback_mode"/>
<desc>
<p>
@@ -558,44 +577,48 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
- <name name="state_entry_mode"/>
+ <name name="state_enter"/>
<desc>
<p>
- The <em>state entry mode</em> is selected when starting the
- <c>gen_statem</c> and after code change
- using the return value from
+ If the state machine should use <em>state enter calls</em>
+ is selected when starting the <c>gen_statem</c>
+ and after code change using the return value from
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>.
</p>
<p>
If
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
- returns a list containing <c>state_entry_events</c>,
+ returns a list containing <c>state_enter</c>,
the <c>gen_statem</c> engine will, at every state change,
- insert an event of type
- <seealso marker="#type-event_type">enter</seealso>
- with content <c>OldState</c>. This event will be inserted
- before all other events such as those generated by
- <seealso marker="#type-action"><c>action()</c></seealso>
- <c>next_event</c>.
+ call the
+ <seealso marker="#state_function">state function</seealso>
+ with arguments <c>(enter, OldState, Data)</c>.
+ This may look like an event but is really a call
+ performed after the previous state function returned
+ and before any event is delivered to the new state function.
+ See
+ <seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>
+ and
+ <seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
</p>
<p>
If
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
- does not return such a list, no state entry events are inserted.
+ does not return such a list, no state enter calls are done.
</p>
<p>
- No state entry event will be inserted after a
+ If
<seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
- since transforming the state to a newer version is regarded
- as staying in the same state even if the newer version state
- should have a different name.
+ should transform the state to a state with a different
+ name it is still regarded as the same state so this
+ does not cause a state enter call.
</p>
<p>
- Note that a state entry event <em>will</em> be inserted
- when entering the initial state even though this formally
- is not a state change. In this case <c>OldState</c>
- will be the same as <c>State</c>, which can not happen
- for an actual state change.
+ Note that a state enter call <em>will</em> be done
+ right before entering the initial state even though this
+ formally is not a state change.
+ In this case <c>OldState</c> will be the same as <c>State</c>,
+ which can not happen for a subsequent state change.
</p>
</desc>
</datatype>
@@ -605,8 +628,7 @@ handle_event(_, _, State, Data) ->
<p>
Transition options can be set by
<seealso marker="#type-action">actions</seealso>
- and they modify the following in how
- the state transition is done:
+ and they modify how the state transition is done:
</p>
<list type="ordered">
<item>
@@ -636,17 +658,22 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-action"><c>action()</c></seealso>
<c>next_event</c>
are inserted in the queue to be processed before
- all other events.
+ other events.
</p>
</item>
<item>
<p>
- If the state changes or is the initial state, and the
- <seealso marker="#type-state_entry_mode"><em>state entry mode</em></seealso>
- is <c>state_entry_events</c>, an event of type
- <seealso marker="#type-event_type">enter</seealso>
- with content <c>OldState</c> is inserted
- to be processed before all other events including those above.
+ If the state changes or is the initial state, and
+ <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>
+ are used, the <c>gen_statem</c> calls
+ the new state function with arguments
+ <seealso marker="#type-state_enter">(enter, OldState, Data)</seealso>.
+ If this call returns any
+ <seealso marker="#type-enter_action"><c>actions</c></seealso>
+ that sets transition options
+ they are merged with the current
+ That is: <c>hibernate</c> and <c>timeout</c> overrides
+ the current and <c>reply</c> sends a reply.
</p>
</item>
<item>
@@ -656,8 +683,9 @@ handle_event(_, _, State, Data) ->
is set through
<seealso marker="#type-action"><c>action()</c></seealso>
<c>timeout</c>,
- an event timer can be started or a time-out zero event
- can be enqueued.
+ an event timer is started if the value is less than
+ <c>infinity</c> or a time-out zero event
+ is enqueued if the value is zero.
</p>
</item>
<item>
@@ -732,7 +760,7 @@ handle_event(_, _, State, Data) ->
is processed before any not yet received external event.
</p>
<p>
- Notice that it is not possible or needed to cancel this time-out,
+ Note that it is not possible or needed to cancel this time-out,
as it is cancelled automatically by any other event.
</p>
</desc>
@@ -743,7 +771,10 @@ handle_event(_, _, State, Data) ->
<p>
These state transition actions can be invoked by
returning them from the
- <seealso marker="#state_function">state function</seealso>, from
+ <seealso marker="#state_function">state function</seealso>
+ when it is called with an
+ <seealso marker="#type-event_type">event</seealso>,
+ from
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or by giving them to
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
@@ -757,8 +788,8 @@ handle_event(_, _, State, Data) ->
override any previous of the same type,
so the last in the containing list wins.
For example, the last
- <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
- overrides any other <c>event_timeout()</c> in the list.
+ <seealso marker="#type-postpone"><c>postpone()</c></seealso>
+ overrides any previous <c>postpone()</c> in the list.
</p>
<taglist>
<tag><c>postpone</c></tag>
@@ -775,6 +806,53 @@ handle_event(_, _, State, Data) ->
as there is no event to postpone in those cases.
</p>
</item>
+ <tag><c>next_event</c></tag>
+ <item>
+ <p>
+ Stores the specified <c><anno>EventType</anno></c>
+ and <c><anno>EventContent</anno></c> for insertion after all
+ actions have been executed.
+ </p>
+ <p>
+ The stored events are inserted in the queue as the next to process
+ before any already queued events. The order of these stored events
+ is preserved, so the first <c>next_event</c> in the containing
+ list becomes the first to process.
+ </p>
+ <p>
+ An event of type
+ <seealso marker="#type-event_type"><c>internal</c></seealso>
+ is to be used when you want to reliably distinguish
+ an event inserted this way from any external event.
+ </p>
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="enter_action"/>
+ <desc>
+ <p>
+ These state transition actions can be invoked by
+ returning them from the
+ <seealso marker="#state_function">state function</seealso>, from
+ <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ or by giving them to
+ <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
+ </p>
+ <p>
+ Actions are executed in the containing list order.
+ </p>
+ <p>
+ Actions that set
+ <seealso marker="#type-transition_option">transition options</seealso>
+ override any previous of the same type,
+ so the last in the containing list wins.
+ For example, the last
+ <seealso marker="#type-event_timeout"><c>event_timeout()</c></seealso>
+ overrides any previous <c>event_timeout()</c> in the list.
+ </p>
+ <taglist>
<tag><c>hibernate</c></tag>
<item>
<p>
@@ -805,32 +883,6 @@ handle_event(_, _, State, Data) ->
to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>.
</p>
</item>
- <tag><c>reply_action()</c></tag>
- <item>
- <p>
- Replies to a caller.
- </p>
- </item>
- <tag><c>next_event</c></tag>
- <item>
- <p>
- Stores the specified <c><anno>EventType</anno></c>
- and <c><anno>EventContent</anno></c> for insertion after all
- actions have been executed.
- </p>
- <p>
- The stored events are inserted in the queue as the next to process
- before any already queued events. The order of these stored events
- is preserved, so the first <c>next_event</c> in the containing
- list becomes the first to process.
- </p>
- <p>
- An event of type
- <seealso marker="#type-event_type"><c>internal</c></seealso>
- is to be used when you want to reliably distinguish
- an event inserted this way from any external event.
- </p>
- </item>
</taglist>
</desc>
</datatype>
@@ -838,13 +890,31 @@ handle_event(_, _, State, Data) ->
<name name="reply_action"/>
<desc>
<p>
- Replies to a caller waiting for a reply in
+ This state transition action can be invoked by
+ returning it from the
+ <seealso marker="#state_function">state function</seealso>, from
+ <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ or by giving it to
+ <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
+ </p>
+ <p>
+ It replies to a caller waiting for a reply in
<seealso marker="#call/2"><c>call/2</c></seealso>.
<c><anno>From</anno></c> must be the term from argument
<seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
- to the
+ in a call to a
<seealso marker="#state_function">state function</seealso>.
</p>
+ <p>
+ Note that using this action from
+ <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ or
+ <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>
+ would be weird on the border of whichcraft
+ since there has been no earlier call to a
+ <seealso marker="#state_function">state function</seealso>
+ in this server.
+ </p>
</desc>
</datatype>
<datatype>
@@ -869,6 +939,27 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="state_function_enter_result"/>
+ <desc>
+ <taglist>
+ <tag><c>next_state</c></tag>
+ <item>
+ <p>
+ The <c>gen_statem</c> does a state transition to
+ <c><anno>NextStateName</anno></c>
+ (which can be the same as the current state),
+ sets <c><anno>NewData</anno></c>,
+ and executes all <c><anno>Actions</anno></c>.
+ </p>
+ </item>
+ </taglist>
+ <p>
+ All these terms are tuples or atoms and this property
+ will hold in any future version of <c>gen_statem</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="handle_event_result"/>
<desc>
<taglist>
@@ -890,6 +981,27 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="handle_event_enter_result"/>
+ <desc>
+ <taglist>
+ <tag><c>next_state</c></tag>
+ <item>
+ <p>
+ The <c>gen_statem</c> does a state transition to
+ <c><anno>NextState</anno></c>
+ (which can be the same as the current state),
+ sets <c><anno>NewData</anno></c>,
+ and executes all <c><anno>Actions</anno></c>.
+ </p>
+ </item>
+ </taglist>
+ <p>
+ All these terms are tuples or atoms and this property
+ will hold in any future version of <c>gen_statem</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="common_state_callback_result"/>
<desc>
<taglist>
@@ -1148,10 +1260,11 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
to the
<seealso marker="#state_function">state function</seealso>.
- <c><anno>From</anno></c> and <c><anno>Reply</anno></c>
- can also be specified using a
- <seealso marker="#type-reply_action"><c>reply_action()</c></seealso>
- and multiple replies with a list of them.
+ A reply or multiple replies canalso be sent
+ using one or several
+ <seealso marker="#type-reply_action"><c>reply_action()</c></seealso>s
+ from a
+ <seealso marker="#state_function">state function</seealso>.
</p>
<note>
<p>
@@ -1362,7 +1475,8 @@ handle_event(_, _, State, Data) ->
once after server start and after code change,
but before the first
<seealso marker="#state_function">state function</seealso>
- is called. More occasions may be added in future versions
+ in the current code version is called.
+ More occasions may be added in future versions
of <c>gen_statem</c>.
</p>
<p>
@@ -1380,7 +1494,7 @@ handle_event(_, _, State, Data) ->
or a list containing
<seealso marker="#type-callback_mode">callback_mode()</seealso>
and possibly the atom
- <seealso marker="#type-state_entry_mode"><c>state_entry_events</c></seealso>.
+ <seealso marker="#type-state_enter"><c>state_enter</c></seealso>.
</p>
<note>
<p>
@@ -1601,7 +1715,8 @@ handle_event(_, _, State, Data) ->
</p>
<p>
The function is to return <c>Status</c>, a term that
- changes the details of the current state and status of
+ contains the appropriate details
+ of the current state and status of
the <c>gen_statem</c>. There are no restrictions on the
form <c>Status</c> can take, but for the
<seealso marker="sys#get_status/1"><c>sys:get_status/1,2</c></seealso>
@@ -1625,11 +1740,17 @@ handle_event(_, _, State, Data) ->
</func>
<func>
+ <name>Module:StateName(enter, OldState, Data) ->
+ StateFunctionEnterResult
+ </name>
<name>Module:StateName(EventType, EventContent, Data) ->
StateFunctionResult
</name>
- <name>Module:handle_event(EventType, EventContent,
- State, Data) -> HandleEventResult
+ <name>Module:handle_event(enter, OldState, State, Data) ->
+ HandleEventResult
+ </name>
+ <name>Module:handle_event(EventType, EventContent, State, Data) ->
+ HandleEventResult
</name>
<fsummary>Handle an event.</fsummary>
<type>
@@ -1651,9 +1772,17 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-state_function_result">state_function_result()</seealso>
</v>
<v>
+ StateFunctionEnterResult =
+ <seealso marker="#type-state_function_enter_result">state_function_enter_result()</seealso>
+ </v>
+ <v>
HandleEventResult =
<seealso marker="#type-handle_event_result">handle_event_result()</seealso>
</v>
+ <v>
+ HandleEventEnterResult =
+ <seealso marker="#type-handle_event_enter_result">handle_event_enter_result()</seealso>
+ </v>
</type>
<desc>
<p>
@@ -1695,6 +1824,24 @@ handle_event(_, _, State, Data) ->
see <seealso marker="#type-action"><c>action()</c></seealso>.
</p>
<p>
+ When the <c>gen_statem</c> runs with
+ <seealso marker="#type-state_enter">state enter calls</seealso>,
+ these functions are also called with arguments
+ <c>(enter, OldState, ...)</c> whenever the state changes.
+ In this case there are some restrictions on the
+ <seealso marker="#type-enter_action">actions</seealso>
+ that may be returned:
+ <seealso marker="#type-postpone"><c>postpone()</c></seealso>
+ and
+ <seealso marker="#type-action"><c>{next_event,_,_}</c></seealso>
+ are not allowed.
+ You may also not change states from this call.
+ Should you return <c>{next_state,NextState, ...}</c>
+ with <c>NextState =/= State</c> the <c>gen_statem</c> crashes.
+ You are advised to use <c>{keep_state,...}</c> or
+ <c>keep_state_and_data</c>.
+ </p>
+ <p>
Note the fact that you can use
<seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>
to return the result, which can be useful.
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 7f437404ed..aedcfc932f 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -47,13 +47,16 @@
%% Type exports for templates
-export_type(
[event_type/0,
- callback_mode/0,
+ state_name/0,
+ callback_mode_result/0,
state_function_result/0,
+ state_function_enter_result/0,
handle_event_result/0,
+ handle_event_enter_result/0,
action/0]).
%% Fix problem for doc build
--export_type([state_entry_mode/0,transition_option/0]).
+-export_type([transition_option/0]).
%%%==========================================================================
%%% Interface functions.
@@ -72,10 +75,12 @@
-type event_type() ::
{'call',From :: from()} | 'cast' |
- 'info' | 'timeout' | 'enter' | 'internal'.
+ 'info' | 'timeout' | 'internal'.
+-type callback_mode_result() ::
+ callback_mode() | [callback_mode() | state_enter()].
-type callback_mode() :: 'state_functions' | 'handle_event_function'.
--type state_entry_mode() :: 'state_entry_events'.
+-type state_enter() :: 'state_enter'.
-type transition_option() ::
postpone() | hibernate() | event_timeout().
@@ -109,6 +114,14 @@
'postpone' | % Set the postpone option
{'postpone', Postpone :: postpone()} |
%%
+ %% All 'next_event' events are kept in a list and then
+ %% inserted at state changes so the first in the
+ %% action() list is the first to be delivered.
+ {'next_event', % Insert event as the next to handle
+ EventType :: event_type(),
+ EventContent :: term()} |
+ enter_action().
+-type enter_action() ::
'hibernate' | % Set the hibernate option
{'hibernate', Hibernate :: hibernate()} |
%%
@@ -116,14 +129,7 @@
{'timeout', % Set the event timeout option
Time :: event_timeout(), EventContent :: term()} |
%%
- reply_action() |
- %%
- %% All 'next_event' events are kept in a list and then
- %% inserted at state changes so the first in the
- %% action() list is the first to be delivered.
- {'next_event', % Insert event as the next to handle
- EventType :: event_type(),
- EventContent :: term()}.
+ reply_action().
-type reply_action() ::
{'reply', % Reply to a caller
From :: from(), Reply :: term()}.
@@ -137,6 +143,16 @@
NewData :: data(),
Actions :: [action()] | action()} |
common_state_callback_result().
+-type state_function_enter_result() ::
+ {'next_state', % {next_state,NextStateName,NewData,[]}
+ NextStateName :: state_name(),
+ NewData :: data()} |
+ {'next_state', % State transition, maybe to the same state
+ NextStateName :: state_name(),
+ NewData :: data(),
+ Actions :: [enter_action()] | enter_action()} |
+ common_state_callback_result().
+
-type handle_event_result() ::
{'next_state', % {next_state,NextState,NewData,[]}
NextState :: state(),
@@ -146,6 +162,16 @@
NewData :: data(),
Actions :: [action()] | action()} |
common_state_callback_result().
+-type handle_event_enter_result() ::
+ {'next_state', % {next_state,NextState,NewData,[]}
+ NextState :: state(),
+ NewData :: data()} |
+ {'next_state', % State transition, maybe to the same state
+ NextState :: state(),
+ NewData :: data(),
+ Actions :: [enter_action()] | enter_action()} |
+ common_state_callback_result().
+
-type common_state_callback_result() ::
'stop' | % {stop,normal}
{'stop', % Stop the server
@@ -164,10 +190,10 @@
NewData :: data()} |
{'keep_state', % Keep state, change data
NewData :: data(),
- Actions :: [action()] | action()} |
+ Actions :: [ActionType] | ActionType} |
'keep_state_and_data' | % {keep_state_and_data,[]}
{'keep_state_and_data', % Keep state and data -> only actions
- Actions :: [action()] | action()}.
+ Actions :: [ActionType] | ActionType}.
%% The state machine init function. It is called only once and
@@ -184,9 +210,7 @@
%%
%% It is called once after init/0 and code_change/4 but before
%% the first state callback StateName/3 or handle_event/4.
--callback callback_mode() ->
- callback_mode() |
- [callback_mode() | state_entry_mode()].
+-callback callback_mode() -> callback_mode_result().
%% Example state callback for StateName = 'state_name'
%% when callback_mode() =:= state_functions.
@@ -197,7 +221,11 @@
%% StateName/3 callbacks and terminate/3, so the state name
%% 'terminate' is unusable in this mode.
-callback state_name(
- event_type(),
+ 'enter',
+ OldStateName :: state_name(),
+ Data :: data()) ->
+ state_function_enter_result();
+ (event_type(),
EventContent :: term(),
Data :: data()) ->
state_function_result().
@@ -205,7 +233,12 @@
%% State callback for all states
%% when callback_mode() =:= handle_event_function.
-callback handle_event(
- event_type(),
+ 'enter',
+ OldState :: state(),
+ State :: state(), % Current state
+ Data :: data()) ->
+ handle_event_enter_result();
+ (event_type(),
EventContent :: term(),
State :: state(), % Current state
Data :: data()) ->
@@ -547,7 +580,7 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
Name = gen:get_proc_name(Server),
Debug = gen:debug_options(Name, Opts),
P = Events = [],
- Event = {internal,initial_state},
+ Event = {internal,init_state},
%% We enforce {postpone,false} to ensure that
%% our fake Event gets discarded, thought it might get logged
NewActions =
@@ -559,7 +592,7 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
end,
S = #{
callback_mode => undefined,
- state_entry_events => false,
+ state_enter => false,
module => Module,
name => Name,
%% The rest of the fields are set from to the arguments to
@@ -886,51 +919,13 @@ loop_events_done(Parent, Debug, S, Timer, State, Data, P, Hibernate) ->
-parse_callback_mode([], CBMode, SEntry) ->
- {CBMode,SEntry};
-parse_callback_mode([H|T], CBMode, SEntry) ->
- case callback_mode(H) of
- true ->
- parse_callback_mode(T, H, SEntry);
- false ->
- case H of
- state_entry_events ->
- parse_callback_mode(T, CBMode, true);
- _ ->
- {undefined,SEntry}
- end
- end;
-parse_callback_mode(_, _CBMode, SEntry) ->
- {undefined,SEntry}.
-
-call_callback_mode(S, CallbackMode) ->
- case
- parse_callback_mode(
- if
- is_atom(CallbackMode) ->
- [CallbackMode];
- true ->
- CallbackMode
- end, undefined, false)
- of
- {undefined,_} ->
- {error,
- {bad_return_from_callback_mode,CallbackMode},
- ?STACKTRACE()};
- {CBMode,SEntry} ->
- {ok,
- S#{
- callback_mode := CBMode,
- state_entry_events := SEntry}}
- end.
-
call_callback_mode(#{module := Module} = S) ->
try Module:callback_mode() of
CallbackMode ->
- call_callback_mode(S, CallbackMode)
+ call_callback_mode_result(S, CallbackMode)
catch
CallbackMode ->
- call_callback_mode(S, CallbackMode);
+ call_callback_mode_result(S, CallbackMode);
error:undef ->
%% Process undef to check for the simple mistake
%% of calling a nonexistent state function
@@ -948,38 +943,57 @@ call_callback_mode(#{module := Module} = S) ->
{Class,Reason,erlang:get_stacktrace()}
end.
-loop_event(
- Parent, Debug,
+call_callback_mode_result(S, CallbackMode) ->
+ case
+ parse_callback_mode(
+ if
+ is_atom(CallbackMode) ->
+ [CallbackMode];
+ true ->
+ CallbackMode
+ end, undefined, false)
+ of
+ {undefined,_} ->
+ {error,
+ {bad_return_from_callback_mode,CallbackMode},
+ ?STACKTRACE()};
+ {CBMode,StateEnter} ->
+ {ok,
+ S#{
+ callback_mode := CBMode,
+ state_enter := StateEnter}}
+ end.
+
+parse_callback_mode([], CBMode, StateEnter) ->
+ {CBMode,StateEnter};
+parse_callback_mode([H|T], CBMode, StateEnter) ->
+ case callback_mode(H) of
+ true ->
+ parse_callback_mode(T, H, StateEnter);
+ false ->
+ case H of
+ state_enter ->
+ parse_callback_mode(T, CBMode, true);
+ _ ->
+ {undefined,StateEnter}
+ end
+ end;
+parse_callback_mode(_, _CBMode, StateEnter) ->
+ {undefined,StateEnter}.
+
+call_state_function(
#{callback_mode := undefined} = S,
- Events,
- State, Data, P, Event, Hibernate) ->
- %% This happens after code_change/4
+ Type, Content, State, Data) ->
case call_callback_mode(S) of
{ok,NewS} ->
- loop_event(
- Parent, Debug, NewS, Events,
- State, Data, P, Event, Hibernate);
- {Class,Reason,Stacktrace} ->
- terminate(
- Class, Reason, Stacktrace,
- Debug, S, [Event|Events], State, Data, P)
+ call_state_function(NewS, Type, Content, State, Data);
+ Error ->
+ Error
end;
-loop_event(
- Parent, Debug,
+call_state_function(
#{callback_mode := CallbackMode,
module := Module} = S,
- Events,
- State, Data, P, {Type,Content} = Event, Hibernate) ->
- %%
- %% If Hibernate is true here it can only be
- %% because it was set from an event action
- %% and we did not go into hibernation since there
- %% were events in queue, so we do what the user
- %% might depend on i.e collect garbage which
- %% would have happened if we actually hibernated
- %% and immediately was awakened
- Hibernate andalso garbage_collect(),
- %%
+ Type, Content, State, Data) ->
try
case CallbackMode of
state_functions ->
@@ -989,12 +1003,10 @@ loop_event(
end
of
Result ->
- loop_event_result(
- Parent, Debug, S, Events, State, Data, P, Event, Result)
+ {ok,Result,S}
catch
Result ->
- loop_event_result(
- Parent, Debug, S, Events, State, Data, P, Event, Result);
+ {ok,Result,S};
error:badarg ->
case erlang:get_stacktrace() of
[{erlang,apply,
@@ -1004,15 +1016,11 @@ loop_event(
when CallbackMode =:= state_functions ->
%% We get here e.g if apply fails
%% due to State not being an atom
- terminate(
- error,
- {undef_state_function,{Module,State,Args}},
- Stacktrace,
- Debug, S, [Event|Events], State, Data, P);
+ {error,
+ {undef_state_function,{Module,State,Args}},
+ Stacktrace};
Stacktrace ->
- terminate(
- error, badarg, Stacktrace,
- Debug, S, [Event|Events], State, Data, P)
+ {error,badarg,Stacktrace}
end;
error:undef ->
%% Process undef to check for the simple mistake
@@ -1022,34 +1030,54 @@ loop_event(
[{Module,State,[Type,Content,Data]=Args,_}
|Stacktrace]
when CallbackMode =:= state_functions ->
- terminate(
- error,
- {undef_state_function,{Module,State,Args}},
- Stacktrace,
- Debug, S, [Event|Events], State, Data, P);
+ {error,
+ {undef_state_function,{Module,State,Args}},
+ Stacktrace};
[{Module,handle_event,[Type,Content,State,Data]=Args,_}
|Stacktrace]
when CallbackMode =:= handle_event_function ->
- terminate(
- error,
- {undef_state_function,{Module,handle_event,Args}},
- Stacktrace,
- Debug, S, [Event|Events], State, Data, P);
+ {error,
+ {undef_state_function,{Module,handle_event,Args}},
+ Stacktrace};
Stacktrace ->
- terminate(
- error, undef, Stacktrace,
- Debug, S, [Event|Events], State, Data, P)
+ {error,undef,Stacktrace}
end;
Class:Reason ->
- Stacktrace = erlang:get_stacktrace(),
+ {Class,Reason,erlang:get_stacktrace()}
+ end.
+
+loop_event(
+ Parent, Debug, S, Events,
+ State, Data, P, {Type,Content} = Event, Hibernate) ->
+ %%
+ %% If Hibernate is true here it can only be
+ %% because it was set from an event action
+ %% and we did not go into hibernation since there
+ %% were events in queue, so we do what the user
+ %% might depend on i.e collect garbage which
+ %% would have happened if we actually hibernated
+ %% and immediately was awakened
+ Hibernate andalso garbage_collect(),
+ case call_state_function(S, Type, Content, State, Data) of
+ {ok,Result,NewS} ->
+ {NewData,NextState,Actions} =
+ parse_event_result(
+ Parent, Debug, NewS, Events,
+ State, Data, P, Event,
+ Result, true),
+ loop_event_actions(
+ Parent, Debug, S, Events,
+ State, NewData, P, Event, NextState, Actions);
+ {Class,Reason,Stacktrace} ->
terminate(
Class, Reason, Stacktrace,
Debug, S, [Event|Events], State, Data, P)
end.
%% Interpret all callback return variants
-loop_event_result(
- Parent, Debug, S, Events, State, Data, P, Event, Result) ->
+parse_event_result(
+ _Parent, Debug, S, Events, State, Data, P, Event,
+ Result, AllowStateChange) ->
case Result of
stop ->
terminate(
@@ -1073,30 +1101,22 @@ loop_event_result(
reply_then_terminate(
exit, Reason, ?STACKTRACE(),
Debug, S, Q, State, NewData, P, Replies);
- {next_state,NextState,NewData} ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, []);
- {next_state,NextState,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions);
+ {next_state,State,NewData} ->
+ {NewData,State,[]};
+ {next_state,NextState,NewData} when AllowStateChange ->
+ {NewData,NextState,[]};
+ {next_state,State,NewData,Actions} ->
+ {NewData,State,Actions};
+ {next_state,NextState,NewData,Actions} when AllowStateChange ->
+ {NewData,NextState,Actions};
{keep_state,NewData} ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, State, []);
+ {NewData,State,[]};
{keep_state,NewData,Actions} ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, State, Actions);
+ {NewData,State,Actions};
keep_state_and_data ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, Data, P, Event, State, []);
+ {Data,State,[]};
{keep_state_and_data,Actions} ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, Data, P, Event, State, Actions);
+ {Data,State,Actions};
_ ->
terminate(
error,
@@ -1105,134 +1125,178 @@ loop_event_result(
Debug, S, [Event|Events], State, Data, P)
end.
-loop_event_actions(
- Parent, Debug, S, Events, State, NewData, P, Event, NextState, Actions) ->
- Postpone = false, % Shall we postpone this event; boolean()
+parse_enter_actions(Debug, S, State, Actions, Hibernate, Timeout) ->
+ Postpone = forbidden,
+ NextEvents = forbidden,
+ parse_actions(
+ Debug, S, State, listify(Actions),
+ Hibernate, Timeout, Postpone, NextEvents).
+
+parse_actions(Debug, S, State, Actions) ->
+ Postpone = false,
Hibernate = false,
Timeout = undefined,
NextEvents = [],
- loop_event_actions(
- Parent, Debug, S, Events, State, NewData, P, Event, NextState,
- if
- is_list(Actions) ->
- Actions;
- true ->
- [Actions]
- end,
- Postpone, Hibernate, Timeout, NextEvents).
+ parse_actions(
+ Debug, S, State, listify(Actions),
+ Hibernate, Timeout, Postpone, NextEvents).
%%
-%% Process all actions
-loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, [Action|Actions],
- Postpone, Hibernate, Timeout, NextEvents) ->
+parse_actions(
+ Debug, _S, _State, [], Hibernate, Timeout, Postpone, NextEvents) ->
+ {ok,Debug,Hibernate,Timeout,Postpone,NextEvents};
+parse_actions(
+ Debug, S, State, [Action|Actions],
+ Hibernate, Timeout, Postpone, NextEvents) ->
case Action of
%% Actual actions
{reply,From,Reply} ->
case from(From) of
true ->
NewDebug = do_reply(Debug, S, State, From, Reply),
- loop_event_actions(
- Parent, NewDebug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, Hibernate, Timeout, NextEvents);
+ parse_actions(
+ NewDebug, S, State, Actions,
+ Hibernate, Timeout, Postpone, NextEvents);
false ->
- terminate(
- error,
- {bad_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug, S, [Event|Events], State, NewData, P)
- end;
- {next_event,Type,Content} ->
- case event_type(Type) of
- true ->
- NewDebug =
- sys_debug(Debug, S, State, {in,{Type,Content}}),
- loop_event_actions(
- Parent, NewDebug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, Hibernate, Timeout,
- [{Type,Content}|NextEvents]);
- false ->
- terminate(
- error,
- {bad_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug, S, [Event|Events], State, NewData, P)
+ {error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE()}
end;
%% Actions that set options
- {postpone,NewPostpone} when is_boolean(NewPostpone) ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- NewPostpone, Hibernate, Timeout, NextEvents);
- {postpone,_} ->
- terminate(
- error,
- {bad_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug, S, [Event|Events], State, NewData, P);
- postpone ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- true, Hibernate, Timeout, NextEvents);
{hibernate,NewHibernate} when is_boolean(NewHibernate) ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, NewHibernate, Timeout, NextEvents);
+ parse_actions(
+ Debug, S, State, Actions,
+ NewHibernate, Timeout, Postpone, NextEvents);
{hibernate,_} ->
- terminate(
- error,
- {bad_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug, S, [Event|Events], State, NewData, P);
+ {error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE()};
hibernate ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, true, Timeout, NextEvents);
+ parse_actions(
+ Debug, S, State, Actions,
+ true, Timeout, Postpone, NextEvents);
{timeout,infinity,_} -> % Clear timer - it will never trigger
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, Hibernate, undefined, NextEvents);
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, undefined, Postpone, NextEvents);
{timeout,Time,_} = NewTimeout when is_integer(Time), Time >= 0 ->
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, Hibernate, NewTimeout, NextEvents);
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, NewTimeout, Postpone, NextEvents);
{timeout,_,_} ->
- terminate(
- error,
- {bad_action_from_state_function,Action},
- ?STACKTRACE(),
- Debug, S, [Event|Events], State, NewData, P);
+ {error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE()};
infinity -> % Clear timer - it will never trigger
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, Hibernate, undefined, NextEvents);
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, undefined, Postpone, NextEvents);
Time when is_integer(Time), Time >= 0 ->
NewTimeout = {timeout,Time,Time},
- loop_event_actions(
- Parent, Debug, S, Events,
- State, NewData, P, Event, NextState, Actions,
- Postpone, Hibernate, NewTimeout, NextEvents);
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, NewTimeout, Postpone, NextEvents);
+ {postpone,NewPostpone}
+ when is_boolean(NewPostpone), Postpone =/= forbidden ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, Timeout, NewPostpone, NextEvents);
+ {postpone,_} ->
+ {error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE()};
+ postpone when Postpone =/= forbidden ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, Timeout, true, NextEvents);
+ {next_event,Type,Content} ->
+ case event_type(Type) of
+ true when NextEvents =/= forbidden ->
+ NewDebug =
+ sys_debug(Debug, S, State, {in,{Type,Content}}),
+ parse_actions(
+ NewDebug, S, State, Actions,
+ Hibernate, Timeout, Postpone,
+ [{Type,Content}|NextEvents]);
+ _ ->
+ {error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE()}
+ end;
_ ->
+ {error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE()}
+ end.
+
+loop_event_actions(
+ Parent, Debug, #{state_enter := StateEnter} = S, Events,
+ State, NewData, P, Event, NextState, Actions) ->
+ case parse_actions(Debug, S, State, Actions) of
+ {ok,NewDebug,Hibernate,Timeout,Postpone,NextEvents} ->
+ case
+ StateEnter andalso
+ ((NextState =/= State)
+ orelse maps:is_key(init_state, S)) of
+ true ->
+ loop_event_enter(
+ Parent, NewDebug, S, Events,
+ State, NewData, P, Event, NextState,
+ Hibernate, Timeout, Postpone, NextEvents);
+ false ->
+ loop_event_result(
+ Parent, NewDebug, S, Events,
+ State, NewData, P, Event, NextState,
+ Hibernate, Timeout, Postpone, NextEvents)
+ end;
+ {Class,Reason,Stacktrace} ->
terminate(
- error,
- {bad_action_from_state_function,Action},
- ?STACKTRACE(),
+ Class, Reason, Stacktrace,
Debug, S, [Event|Events], State, NewData, P)
- end;
-%%
-%% End of actions list
-loop_event_actions(
- Parent, Debug, #{state_entry_events := SEEvents} = S, Events,
- State, NewData, P0, Event, NextState, [],
- Postpone, Hibernate, Timeout, NextEvents) ->
+ end.
+
+loop_event_enter(
+ Parent, Debug, S, Events,
+ State, NewData, P, Event, NextState,
+ Hibernate, Timeout, Postpone, NextEvents) ->
+ case call_state_function(S, enter, State, NextState, NewData) of
+ {ok,Result,NewS} ->
+ {NewerData,_,Actions} =
+ parse_event_result(
+ Parent, Debug, NewS, Events,
+ NextState, NewData, P, Event,
+ Result, false),
+ loop_event_enter_actions(
+ Parent, Debug, NewS, Events,
+ State, NewerData, P, Event, NextState,
+ Hibernate, Timeout, Postpone, NextEvents, Actions);
+ {Class,Reason,Stacktrace} ->
+ terminate(
+ Class, Reason, Stacktrace,
+ Debug, S, [Event|Events], NextState, NewData, P)
+ end.
+
+loop_event_enter_actions(
+ Parent, Debug, S, Events,
+ State, NewData, P, Event, NextState,
+ Hibernate, Timeout, Postpone, NextEvents, Actions) ->
+ case
+ parse_enter_actions(Debug, S, NextState, Actions, Hibernate, Timeout)
+ of
+ {ok,NewDebug,NewHibernate,NewTimeout,_,_} ->
+ loop_event_result(
+ Parent, NewDebug, S, Events,
+ State, NewData, P, Event, NextState,
+ NewHibernate, NewTimeout, Postpone, NextEvents);
+ {Class,Reason,Stacktrace} ->
+ terminate(
+ Class, Reason, Stacktrace,
+ Debug, S, [Event|Events], NextState, NewData, P)
+ end.
+
+loop_event_result(
+ Parent, Debug, S, Events,
+ State, NewData, P0, Event, NextState,
+ Hibernate, Timeout, Postpone, NextEvents) ->
%%
%% All options have been collected and next_events are buffered.
%% Do the actual state transition.
@@ -1252,44 +1316,21 @@ loop_event_actions(
{lists:reverse(P1, Events),[]}
end,
%% Place next events first in queue
- Q3 = lists:reverse(NextEvents, Q2),
- %% State entry events
- Q =
- case SEEvents of
- true ->
- %% Generate state entry events
- case
- (NextState =/= State)
- orelse maps:is_key(init_state, S)
- of
- true ->
- %% State change or initial state
- [{enter,State}|Q3];
- false ->
- Q3
- end;
- false ->
- Q3
- end,
+ Q = lists:reverse(NextEvents, Q2),
%%
NewDebug =
sys_debug(
Debug, S, State,
case Postpone of
true ->
- {postpone,Event,NextState};
+ {postpone,Event,State};
false ->
- {consume,Event,NextState}
+ {consume,Event,State}
end),
loop_events(
Parent, NewDebug,
%% Avoid infinite loop in initial state with state entry events
- case maps:is_key(init_state, S) of
- true ->
- maps:remove(init_state, S);
- false ->
- S
- end,
+ maps:remove(init_state, S),
Q, NextState, NewData, P, Hibernate, Timeout).
%%---------------------------------------------------------------------------
@@ -1369,7 +1410,7 @@ error_info(
Class, Reason, Stacktrace,
#{name := Name,
callback_mode := CallbackMode,
- state_entry_events := SEEvents},
+ state_enter := StateEnter},
Q, P, FmtData) ->
{FixedReason,FixedStacktrace} =
case Stacktrace of
@@ -1397,9 +1438,9 @@ error_info(
_ -> {Reason,Stacktrace}
end,
CBMode =
- case SEEvents of
+ case StateEnter of
true ->
- [CallbackMode,state_entry_events];
+ [CallbackMode,state_enter];
false ->
CallbackMode
end,
@@ -1471,3 +1512,8 @@ format_status_default(Opt, State, Data) ->
_ ->
[{data,[{"State",StateData}]}]
end.
+
+listify(Item) when is_list(Item) ->
+ Item;
+listify(Item) ->
+ [Item].
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index eef8f265c4..48f93b1de7 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -37,7 +37,7 @@ all() ->
{group, stop_handle_event},
{group, abnormal},
{group, abnormal_handle_event},
- shutdown, stop_and_reply, enter_events, event_order, code_change,
+ shutdown, stop_and_reply, state_enter, event_order, code_change,
{group, sys},
hibernate, enter_loop].
@@ -582,7 +582,7 @@ stop_and_reply(_Config) ->
-enter_events(_Config) ->
+state_enter(_Config) ->
process_flag(trap_exit, true),
Self = self(),
@@ -615,7 +615,7 @@ enter_events(_Config) ->
end},
{ok,STM} =
gen_statem:start_link(
- ?MODULE, {map_statem,Machine,[state_entry_events]}, []),
+ ?MODULE, {map_statem,Machine,[state_enter]}, []),
[{enter,start,start,1}] = flush(),
{echo,start,2} = gen_statem:call(STM, echo),
diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el
index 0284c9d686..95cc989c73 100644
--- a/lib/tools/emacs/erlang-skels.el
+++ b/lib/tools/emacs/erlang-skels.el
@@ -904,7 +904,7 @@ Please see the function `tempo-define-template'.")
"%% @doc" n
"%% Define the callback_mode() for this callback module." n
(erlang-skel-separator-end 2)
- "-spec callback_mode() -> gen_statem:callback_mode()." n
+ "-spec callback_mode() -> gen_statem:callback_mode_result()." n
"callback_mode() -> state_functions." n
n
(erlang-skel-separator-start 2)
@@ -936,6 +936,10 @@ Please see the function `tempo-define-template'.")
"%% instead of StateName/3 functions like this!" n
(erlang-skel-separator-end 2)
"-spec state_name(" n>
+ "'enter', OldState :: gen_statem:state_name()," n>
+ "Data :: term()) ->" n>
+ "gen_statem:state_function_enter_result();" n
+ " (" n>
"gen_statem:event_type(), Msg :: term()," n>
"Data :: term()) ->" n>
"gen_statem:state_function_result()." n
@@ -1015,7 +1019,7 @@ Please see the function `tempo-define-template'.")
"%% @doc" n
"%% Define the callback_mode() for this callback module." n
(erlang-skel-separator-end 2)
- "-spec callback_mode() -> gen_statem:callback_mode()." n
+ "-spec callback_mode() -> gen_statem:callback_mode_result()." n
"callback_mode() -> handle_event_function." n
n
(erlang-skel-separator-start 2)
@@ -1044,6 +1048,10 @@ Please see the function `tempo-define-template'.")
"%% StateName/3 functions are called instead!" n
(erlang-skel-separator-end 2)
"-spec handle_event(" n>
+ "'enter', OldState :: term()," n>
+ "State :: term(), Data :: term()) ->" n>
+ "gen_statem:handle_event_enter_result();" n
+ " (" n>
"gen_statem:event_type(), Msg :: term()," n>
"State :: term(), Data :: term()) ->" n>
"gen_statem:handle_event_result()." n
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 565b0e5274..d2a9b23570 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -195,21 +195,19 @@ handle_event(EventType, EventContent, State, Data) ->
<!-- =================================================================== -->
<section>
- <marker id="state_entry_events" />
- <title>State Entry Events</title>
+ <marker id="state_enter" />
+ <title>State Enter Calls</title>
<p>
The <c>gen_statem</c> behavior can regardless of callback mode
- automatically generate an
- <seealso marker="stdlib:gen_statem#type-state_entry_mode">
- event whenever the state changes
- </seealso>
- so you can write state entry code
+ automatically call the state function
+ with special arguments whenever the state changes
+ so you can write state entry actions
near the rest of the state transition rules.
It typically looks like this:
</p>
<pre>
StateName(enter, _OldState, Data) ->
- ... code for state entry here ...
+ ... code for state entry actions here ...
{keep_state, NewData};
StateName(EventType, EventContent, Data) ->
... code for actions here ...
@@ -217,7 +215,7 @@ StateName(EventType, EventContent, Data) ->
<p>
Depending on how your state machine is specified,
this can be a very useful feature, but if you use it
- you will have to handle the state entry events in all states.
+ you will have to handle the state enter call in all states.
</p>
</section>
@@ -751,12 +749,6 @@ stop() ->
Generated by any regular process message sent to
the <c>gen_statem</c> process.
</item>
- <tag><c>enter</c></tag>
- <item>
- Generated by a state transition with
- <c>OldState =/= NewState</c> when running with
- <seealso marker="#state_entry_events">state entry events</seealso>.
- </item>
<tag><c>timeout</c></tag>
<item>
Generated by state transition action
@@ -972,63 +964,35 @@ do_unlock() ->
<!-- =================================================================== -->
<section>
- <title>Self-Generated Events</title>
- <p>
- It can sometimes be beneficial to be able to generate events
- to your own state machine.
- This can be done with the state transition
- <seealso marker="stdlib:gen_statem#type-action">action</seealso>
- <c>{next_event,EventType,EventContent}</c>.
- </p>
+ <title>State Entry Actions</title>
<p>
- You can generate events of any existing
- <seealso marker="stdlib:gen_statem#type-action">type</seealso>,
- but the <c>internal</c> type can only be generated through action
- <c>next_event</c>. Hence, it cannot come from an external source,
- so you can be certain that an <c>internal</c> event is an event
- from your state machine to itself.
+ Say you have a state machine specification
+ that uses state entry actions.
+ Allthough you can code this using self-generated events
+ (described in the next section), especially if just
+ one or a few states has got state entry actions,
+ this is a perfect use case for the built in
+ <seealso marker="#state_enter">state enter calls</seealso>.
</p>
<p>
- One example for this is to pre-process incoming data, for example
- decrypting chunks or collecting characters up to a line break.
- This could be modelled with a separate state machine that sends
- the pre-processed events to the main state machine, or to decrease
- overhead the small pre-processing state machine can be implemented
- in the common state event handling of the main state machine
- using a few state data variables and then send the pre-processed
- events as internal events to the main state machine.
- </p>
- <p>
- Another example of using self-generated events can be when you have
- a state machine specification that uses state entry actions.
- You can code that using a dedicated function
- to do the state transition. But if you want that code to be
- visible besides the other state logic, you can insert
- an <c>internal</c> event that does the entry actions.
- This has the same unfortunate consequence as using
- state transition functions: everywhere you go to
- the state, you must explicitly
- insert the <c>internal</c> event
- or use a state transition function.
- This is something that can be forgotten, and if you find that
- annoying please look at the next chapter.
- </p>
- <p>
- The following is an implementation of entry actions
- using <c>internal</c> events with content <c>enter</c>
- using a helper function <c>enter/3</c> for state entry:
+ You return a list containing <c>state_enter</c> from your
+ <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>callback_mode/0</c></seealso>
+ function and the <c>gen_statem</c> engine will call your
+ state function once with the arguments
+ <c>(enter, OldState, ...)</c> whenever the state changes.
+ Then you just need to handle these event-like calls in all states.
</p>
<code type="erl"><![CDATA[
...
init(Code) ->
process_flag(trap_exit, true),
Data = #{code => Code},
- enter(ok, locked, Data).
+ {ok, locked, Data}.
callback_mode() ->
- state_functions.
+ [state_functions,state_enter].
-locked(internal, enter, Data) ->
+locked(enter, _OldState, Data) ->
do_lock(),
{keep_state,Data#{remaining => Code}};
locked(
@@ -1036,79 +1000,94 @@ locked(
#{code := Code, remaining := Remaining} = Data) ->
case Remaining of
[Digit] ->
- enter(next_state, open, Data);
+ {next_state, open, Data};
...
-open(internal, enter, Data) ->
+open(enter, _OldState, Data) ->
Tref = erlang:start_timer(10000, self(), lock),
do_unlock(),
{keep_state,Data#{timer => Tref}};
open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
- enter(next_state, locked, Data);
+ {next_state, locked, Data};
...
-
-enter(Tag, State, Data) ->
- {Tag,State,Data,[{next_event,internal,enter}]}.
]]></code>
</section>
<!-- =================================================================== -->
<section>
- <title>Using State Entry Events</title>
+ <title>Self-Generated Events</title>
<p>
- Here is the same example as the previous but instead using
- the built in
- <seealso marker="#state_entry_events">state entry events</seealso>.
+ It can sometimes be beneficial to be able to generate events
+ to your own state machine.
+ This can be done with the state transition
+ <seealso marker="stdlib:gen_statem#type-action">action</seealso>
+ <c>{next_event,EventType,EventContent}</c>.
</p>
<p>
- Since the state entry events are unconditionally inserted by
- the <c>gen_statem</c> engine you can not forget to insert them
- yourself and you will have to handle the state entry events
- in every state.
+ You can generate events of any existing
+ <seealso marker="stdlib:gen_statem#type-action">type</seealso>,
+ but the <c>internal</c> type can only be generated through action
+ <c>next_event</c>. Hence, it cannot come from an external source,
+ so you can be certain that an <c>internal</c> event is an event
+ from your state machine to itself.
</p>
<p>
- If you want state entry code in just a few states the previous
- example may be more suitable, especially to only send internal
- events when entering just those few states.
- Note: additional discipline will be required.
+ One example for this is to pre-process incoming data, for example
+ decrypting chunks or collecting characters up to a line break.
+ Purists may argue that this should be modelled with a separate
+ state machine that sends pre-processed events
+ to the main state machine.
+ But to decrease overhead the small pre-processing state machine
+ can be implemented in the common state event handling
+ of the main state machine using a few state data variables
+ that then sends the pre-processed events as internal events
+ to the main state machine.
</p>
<p>
- You can also in the previous example choose to generate
- events looking just like the events you get from using
- <seealso marker="#state_entry_events">state entry events</seealso>.
- This may be confusing, or practical,
- depending on your point of view.
+ The following example use an input model where you give the lock
+ characters with <c>put_chars(Chars)</c> and then call
+ <c>enter()</c> to finish the input.
</p>
<code type="erl"><![CDATA[
...
-init(Code) ->
- process_flag(trap_exit, true),
- Data = #{code => Code},
- {ok, locked, Data}.
+-export(put_chars/1, enter/0).
+...
+put_chars(Chars) when is_binary(Chars) ->
+ gen_statem:call(?NAME, {chars,Chars}).
-callback_mode() ->
- [state_functions,state_entry_events].
+enter() ->
+ gen_statem:call(?NAME, enter).
+
+...
locked(enter, _OldState, Data) ->
do_lock(),
- {keep_state,Data#{remaining => Code}};
-locked(
- cast, {button,Digit},
- #{code := Code, remaining := Remaining} = Data) ->
- case Remaining of
- [Digit] ->
- {next_state, open, Data};
+ {keep_state,Data#{remaining => Code, buf => []}};
...
-open(enter, _OldState, Data) ->
- Tref = erlang:start_timer(10000, self(), lock),
- do_unlock(),
- {keep_state,Data#{timer => Tref}};
-open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
- {next_state, locked, Data};
+handle_event({call,From}, {chars,Chars}, #{buf := Buf} = Data) ->
+ {keep_state, Data#{buf := [Chars|Buf],
+ [{reply,From,ok}]};
+handle_event({call,From}, enter, #{buf := Buf} = Data) ->
+ Chars = unicode:characters_to_binary(lists:reverse(Buf)),
+ try binary_to_integer(Chars) of
+ Digit ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,ok},
+ {next_event,internal,{button,Chars}}]}
+ catch
+ error:badarg ->
+ {keep_state, Data#{buf := []},
+ [{reply,From,{error,not_an_integer}}]}
+ end;
...
]]></code>
+ <p>
+ If you start this program with <c>code_lock:start([17])</c>
+ you can unlock with <c>code_lock:put_chars(&lt;&lt;"001">>),
+ code_lock:put_chars(&lt;&lt;"7">>), code_lock:enter()</c>.
+ </p>
</section>
<!-- =================================================================== -->
@@ -1117,7 +1096,7 @@ open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
<title>Example Revisited</title>
<p>
This section includes the example after all mentioned modifications
- and some more using the entry actions,
+ and some more using state enter calls,
which deserves a new state diagram:
</p>
<image file="../design_principles/code_lock_2.png">
@@ -1163,7 +1142,7 @@ init(Code) ->
{ok, locked, Data}.
callback_mode() ->
- [state_functions,state_entry_events].
+ [state_functions,state_enter].
locked(enter, _OldState, #{code := Code} = Data) ->
do_lock(),
@@ -1215,8 +1194,7 @@ code_change(_Vsn, State, Data, _Extra) ->
This section describes what to change in the example
to use one <c>handle_event/4</c> function.
The previously used approach to first branch depending on event
- does not work that well here because of
- the state entry events,
+ does not work that well here because of the state enter calls,
so this example first branches depending on state:
</p>
<code type="erl"><![CDATA[
@@ -1225,7 +1203,7 @@ code_change(_Vsn, State, Data, _Extra) ->
...
callback_mode() ->
- [handle_event_function,state_entry_events].
+ [handle_event_function,state_enter].
%% State: locked
handle_event(enter, _OldState, locked, #{code := Code} = Data) ->
@@ -1413,7 +1391,7 @@ init({Code,LockButton}) ->
{ok, {locked,LockButton}, Data}.
callback_mode() ->
- [handle_event_function,state_entry_events].
+ [handle_event_function,state_enter].
handle_event(
{call,From}, {set_lock_button,NewLockButton},