aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib')
-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
3 files changed, 543 insertions, 350 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),