aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2018-04-24 15:36:14 +0200
committerRaimo Niskanen <[email protected]>2018-04-24 15:36:14 +0200
commit685cd438232282ee0fa790517e7fe193110ff265 (patch)
tree34f97b67026443afb1fccf75d6194884da48a6b5 /lib/stdlib
parent4b9d0a6e768966722f460d8bfa381b739165297f (diff)
parent728bc036aa72a83080e933722f8ad409ede69f70 (diff)
downloadotp-685cd438232282ee0fa790517e7fe193110ff265.tar.gz
otp-685cd438232282ee0fa790517e7fe193110ff265.tar.bz2
otp-685cd438232282ee0fa790517e7fe193110ff265.zip
Merge branch 'raimo/stdlib/gen_statem-dev/OTP-14015'
* raimo/stdlib/gen_statem-dev/OTP-14015: Fix after feedback Improve pointer to User's Guide Fix after feedback on 'When to use' Add a 'When to use' section Fix timeout parsing and doc feedback Improve doc, change images to .svg erl_docgen: Implement width in image tag Update User's Guide and pointers to it Improve error reasons from state enter call
Diffstat (limited to 'lib/stdlib')
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml219
-rw-r--r--lib/stdlib/src/gen_statem.erl120
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl46
3 files changed, 268 insertions, 117 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index be0d64feba..e918e83df7 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -32,7 +32,68 @@
<modulesummary>Generic state machine behavior.</modulesummary>
<description>
<p>
- This behavior module provides a state machine. Two
+ <c>gen_statem</c> provides a generic state machine behaviour
+ and replaces its predecessor
+ <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
+ since Erlang/OTP 20.0.
+ </p>
+ <p>
+ This reference manual describes types generated from the types
+ in the <c>gen_statem</c> source code, so they are correct.
+ However, the generated descriptions also reflect the type hiearchy,
+ which makes them kind of hard to read.
+ </p>
+ <p>
+ To get an overview of the concepts and operation of <c>gen_statem</c>,
+ do read the
+ <seealso marker="doc/design_principles:statem">
+ <c>gen_statem</c>&nbsp;Behaviour
+ </seealso>
+ in
+ <seealso marker="doc/design_principles:users_guide">
+ OTP Design Principles
+ </seealso>
+ which frequently links back to this reference manual to avoid
+ containing detailed facts that may rot by age.
+ </p>
+ <note>
+ <p>
+ This behavior appeared in Erlang/OTP 19.0.
+ In OTP 19.1 a backwards incompatible change of
+ the return tuple from
+ <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ was made and the mandatory callback function
+ <seealso marker="#Module:callback_mode/0">
+ <c>Module:callback_mode/0</c>
+ </seealso>
+ was introduced. In OTP 20.0 the
+ <seealso marker="#type-generic_timeout"><c>generic timeouts</c></seealso>
+ were added.
+ </p>
+ </note>
+ <p>
+ <c>gen_statem</c> has got the same features that
+ <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>
+ had and adds some really useful:
+ </p>
+ <list type="bulleted">
+ <item>Co-located state code</item>
+ <item>Arbitrary term state</item>
+ <item>Event postponing</item>
+ <item>Self-generated events</item>
+ <item>State time-out</item>
+ <item>Multiple generic named time-outs</item>
+ <item>Absolute time-out time</item>
+ <item>Automatic state enter calls</item>
+ <item>
+ Reply from other state than the request, <c>sys</c> traceable
+ </item>
+ <item>Multiple <c>sys</c> traceable replies</item>
+ </list>
+
+
+ <p>
+ Two
<seealso marker="#type-callback_mode"><em>callback modes</em></seealso>
are supported:
</p>
@@ -50,34 +111,6 @@
</p>
</item>
</list>
- <note>
- <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.
- 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.
- </p>
- </note>
- <p>
- The <c>gen_statem</c> behavior replaces
- <seealso marker="gen_fsm"><c>gen_fsm</c> </seealso> in Erlang/OTP 20.0.
- It has the same features and adds some really useful:
- </p>
- <list type="bulleted">
- <item>Gathered state code.</item>
- <item>Arbitrary term state.</item>
- <item>Event postponing.</item>
- <item>Self-generated events.</item>
- <item>State time-out.</item>
- <item>Multiple generic named time-outs.</item>
- <item>Absolute time-out time.</item>
- <item>Automatic state enter calls.</item>
- <item>Reply from other state than the request.</item>
- <item>Multiple <c>sys</c> traceable replies.</item>
- </list>
<p>
The callback model(s) for <c>gen_statem</c> differs from
the one for <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>,
@@ -148,7 +181,7 @@ erlang:'!' -----> Module:StateName/3
is <c>state_functions</c>, the state must be an atom and
is used as the state callback name; see
<seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>.
- This gathers all code for a specific state
+ This co-locates all code for a specific state
in one function as the <c>gen_statem</c> engine
branches depending on state name.
Note the fact that the callback function
@@ -207,8 +240,10 @@ erlang:'!' -----> Module:StateName/3
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.
+ Another way to do it is to insert an event at the state transition,
+ and/or to use a dedicated state transition function,
+ but that is something you will have to remember
+ at every state transition to the state(s) that need it.
</p>
<note>
<p>If you in <c>gen_statem</c>, for example, postpone
@@ -252,6 +287,16 @@ erlang:'!' -----> Module:StateName/3
to use after every event; see
<seealso marker="erts:erlang#hibernate/3"><c>erlang:hibernate/3</c></seealso>.
</p>
+ <p>
+ There is also a server start option
+ <seealso marker="#type-hibernate_after_opt">
+ <c>{hibernate_after, Timeout}</c>
+ </seealso>
+ for
+ <seealso marker="#start/3"><c>start/3,4</c></seealso> or
+ <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>
+ that may be used to automatically hibernate the server.
+ </p>
</description>
<section>
@@ -668,9 +713,9 @@ handle_event(_, _, State, Data) ->
<p>
If
<seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
- 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.
+ should transform the state,
+ it is regarded as a state rename and not a state change,
+ which will not cause a state enter call.
</p>
<p>
Note that a state enter call <em>will</em> be done
@@ -688,12 +733,19 @@ handle_event(_, _, State, Data) ->
<p>
Transition options can be set by
<seealso marker="#type-action">actions</seealso>
- and they modify how the state transition is done:
+ and modify the state transition.
+ Here are the sequence of steps for a state transition:
</p>
<list type="ordered">
<item>
<p>
- If the state changes, is the initial state,
+ If
+ <seealso marker="#type-state_enter">
+ <em>state enter calls</em>
+ </seealso>
+ are used, and either:
+ the state changes, it is the initial state,
+ or one of the callback results
<seealso marker="#type-state_callback_result">
<c>repeat_state</c>
</seealso>
@@ -701,16 +753,21 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-state_callback_result">
<c>repeat_state_and_data</c>
</seealso>
- is used, and also
- <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>
- are used, the <c>gen_statem</c> calls
+ is used; the <c>gen_statem</c> calls
the new state callback with arguments
- <seealso marker="#type-state_enter">(enter, OldState, Data)</seealso>.
+ <seealso marker="#type-state_enter"><c>(enter, OldState, Data)</c></seealso>.
+ </p>
+ <p>
Any
- <seealso marker="#type-enter_action"><c>actions</c></seealso>
+ <seealso marker="#type-enter_action">actions</seealso>
returned from this call are handled as if they were
- appended to the actions
- returned by the state callback that changed states.
+ appended to the actions
+ returned by the state callback that caused the state entry.
+ </p>
+ <p>
+ Should this state enter call return any of
+ the mentioned <c>repeat_*</c> callback results
+ it is repeated again, with the updated <c>Data</c>.
</p>
</item>
<item>
@@ -739,7 +796,7 @@ handle_event(_, _, State, Data) ->
All events stored with
<seealso marker="#type-action"><c>action()</c></seealso>
<c>next_event</c>
- are inserted to be processed before the other queued events.
+ are inserted to be processed before previously queued events.
</p>
</item>
<item>
@@ -753,7 +810,9 @@ handle_event(_, _, State, Data) ->
delivered to the state machine before any external
not yet received event so if there is such a time-out requested,
the corresponding time-out zero event is enqueued as
- the newest event.
+ the newest received event;
+ that is after already queued events
+ such as inserted and postponed events.
</p>
<p>
Any event cancels an
@@ -791,7 +850,7 @@ handle_event(_, _, State, Data) ->
When a new message arrives the
<seealso marker="#state callback">state callback</seealso>
is called with the corresponding event,
- and we start again from the top of this list.
+ and we start again from the top of this sequence.
</p>
</item>
</list>
@@ -816,13 +875,19 @@ handle_event(_, _, State, Data) ->
<seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>
before going into <c>receive</c>
to wait for a new external event.
- If there are enqueued events,
- to prevent receiving any new event, an
- <seealso marker="erts:erlang#garbage_collect/0"><c>erlang:garbage_collect/0</c></seealso>
- is done instead to simulate
- that the <c>gen_statem</c> entered hibernation
- and immediately got awakened by the oldest enqueued event.
</p>
+ <note>
+ <p>
+ If there are enqueued events to process
+ when hibrnation is requested,
+ this is optimized by not hibernating but instead calling
+ <seealso marker="erts:erlang#garbage_collect/0">
+ <c>erlang:garbage_collect/0</c>
+ </seealso>
+ to simulate that the <c>gen_statem</c> entered hibernation
+ and immediately got awakened by an enqueued event.
+ </p>
+ </note>
</desc>
</datatype>
<datatype>
@@ -857,7 +922,7 @@ handle_event(_, _, State, Data) ->
no timer is actually started,
instead the the time-out event is enqueued to ensure
that it gets processed before any not yet
- received external event.
+ received external event, but after already queued events.
</p>
<p>
Note that it is not possible nor needed to cancel this time-out,
@@ -943,7 +1008,9 @@ handle_event(_, _, State, Data) ->
If <c>Abs</c> is <c>true</c> an absolute timer is started,
and if it is <c>false</c> a relative, which is the default.
See
- <seealso marker="erts:erlang#start_timer/4"><c>erlang:start_timer/4</c></seealso>
+ <seealso marker="erts:erlang#start_timer/4">
+ <c>erlang:start_timer/4</c>
+ </seealso>
for details.
</p>
<p>
@@ -969,7 +1036,9 @@ handle_event(_, _, State, Data) ->
</p>
<p>
Actions that set
- <seealso marker="#type-transition_option">transition options</seealso>
+ <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
@@ -981,7 +1050,9 @@ handle_event(_, _, State, Data) ->
<item>
<p>
Sets the
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
+ <seealso marker="#type-transition_option">
+ <c>transition_option()</c>
+ </seealso>
<seealso marker="#type-postpone"><c>postpone()</c></seealso>
for this state transition.
This action is ignored when returned from
@@ -994,7 +1065,11 @@ handle_event(_, _, State, Data) ->
<tag><c>next_event</c></tag>
<item>
<p>
- Stores the specified <c><anno>EventType</anno></c>
+ This action does not set any
+ <seealso marker="#type-transition_option">
+ <c>transition_option()</c>
+ </seealso>
+ but instead stores the specified <c><anno>EventType</anno></c>
and <c><anno>EventContent</anno></c> for insertion after all
actions have been executed.
</p>
@@ -1066,15 +1141,15 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-transition_option">transition options</seealso>.
</p>
<taglist>
- <tag><c>Timeout</c></tag>
+ <tag><c>Time</c></tag>
<item>
<p>
- Short for <c>{timeout,Timeout,Timeout}</c>, that is,
+ Short for <c>{timeout,Time,Time}</c>, that is,
the time-out message is the time-out time.
This form exists to make the
<seealso marker="#state callback">state callback</seealso>
- return value <c>{next_state,NextState,NewData,Timeout}</c>
- allowed like for <c>gen_fsm</c>'s
+ return value <c>{next_state,NextState,NewData,Time}</c>
+ allowed like for <c>gen_fsm</c>.
</p>
</item>
<tag><c>timeout</c></tag>
@@ -1126,7 +1201,11 @@ handle_event(_, _, State, Data) ->
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
</p>
<p>
- It replies to a caller waiting for a reply in
+ It does not set any
+ <seealso marker="#type-transition_option">
+ <c>transition_option()</c>
+ </seealso>
+ but instead 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>
@@ -2109,16 +2188,20 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
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.
- It is possible to use <c>{repeat_state, ...}</c>,
- <c>{repeat_state_and_data,_}</c> or
- <c>repeat_state_and_data</c> but all of them makes little
+ Note that it is actually allowed to use
+ <c>{repeat_state, NewData, ...}</c> although it makes little
sense since you immediately will be called again with a new
<em>state enter call</em> making this just a weird way
of looping, and there are better ways to loop in Erlang.
+ If you do not update <c>NewData</c> and have some
+ loop termination condition, or if you use
+ <c>{repeat_state_and_data, _}</c> or
+ <c>repeat_state_and_data</c> you have an infinite loop!
You are advised to use <c>{keep_state,...}</c>,
<c>{keep_state_and_data,_}</c> or
- <c>keep_state_and_data</c> since you can not change states
- from a <em>state enter call</em> anyway.
+ <c>keep_state_and_data</c>
+ since changing states from a <em>state enter call</em>
+ is not possible anyway.
</p>
<p>
Note the fact that you can use
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 7f5d82cc21..f7dc0050b3 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -143,7 +143,7 @@
timeout_action() |
reply_action().
-type timeout_action() ::
- (Timeout :: event_timeout()) | % {timeout,Timeout}
+ (Time :: event_timeout()) | % {timeout,Time,Time}
{'timeout', % Set the event_timeout option
Time :: event_timeout(), EventContent :: term()} |
{'timeout', % Set the event_timeout option
@@ -327,7 +327,8 @@
%% Type validation functions
-compile(
{inline,
- [callback_mode/1, state_enter/1, from/1, event_type/1]}).
+ [callback_mode/1, state_enter/1,
+ event_type/1, from/1, timeout_event_type/1]}).
%%
callback_mode(CallbackMode) ->
case CallbackMode of
@@ -344,23 +345,26 @@ state_enter(StateEnter) ->
false
end.
%%
-from({Pid,_}) when is_pid(Pid) -> true;
-from(_) -> false.
-%%
-event_type({call,From}) ->
- from(From);
event_type(Type) ->
case Type of
{call,From} -> from(From);
+ %%
cast -> true;
info -> true;
- timeout -> true;
- state_timeout -> true;
internal -> true;
- {timeout,_} -> true;
- _ -> false
+ _ -> timeout_event_type(Type)
+ end.
+%%
+from({Pid,_}) when is_pid(Pid) -> true;
+from(_) -> false.
+%%
+timeout_event_type(Type) ->
+ case Type of
+ timeout -> true;
+ state_timeout -> true;
+ {timeout,_Name} -> true;
+ _ -> false
end.
-
-define(
@@ -1056,6 +1060,15 @@ loop_event_result(
Parent, Debug, S,
Events, Event, NextState, NewData, TransOpts,
[], true);
+ {next_state,_NextState,_NewData} ->
+ terminate(
+ error,
+ {bad_state_enter_return_from_state_function,Result},
+ ?STACKTRACE(), Debug,
+ S#state{
+ state = State, data = Data,
+ hibernate = hibernate_in_trans_opts(TransOpts)},
+ [Event|Events]);
{next_state,State,NewData,Actions} ->
loop_event_actions(
Parent, Debug, S,
@@ -1067,6 +1080,15 @@ loop_event_result(
Parent, Debug, S,
Events, Event, NextState, NewData, TransOpts,
Actions, true);
+ {next_state,_NextState,_NewData,_Actions} ->
+ terminate(
+ error,
+ {bad_state_enter_return_from_state_function,Result},
+ ?STACKTRACE(), Debug,
+ S#state{
+ state = State, data = Data,
+ hibernate = hibernate_in_trans_opts(TransOpts)},
+ [Event|Events]);
%%
{keep_state,NewData} ->
loop_event_actions(
@@ -1160,12 +1182,6 @@ loop_event_result(
[Event|Events])
end.
--compile({inline, [hibernate_in_trans_opts/1]}).
-hibernate_in_trans_opts(false) ->
- (#trans_opts{})#trans_opts.hibernate;
-hibernate_in_trans_opts(#trans_opts{hibernate = Hibernate}) ->
- Hibernate.
-
%% Ensure that Actions are a list
loop_event_actions(
Parent, Debug, S,
@@ -1198,10 +1214,16 @@ loop_event_actions_list(
S#state{
state = NextState,
data = NewerData,
- hibernate = TransOpts#trans_opts.hibernate},
+ hibernate = hibernate_in_trans_opts(TransOpts)},
[Event|Events])
end.
+-compile({inline, [hibernate_in_trans_opts/1]}).
+hibernate_in_trans_opts(false) ->
+ (#trans_opts{})#trans_opts.hibernate;
+hibernate_in_trans_opts(#trans_opts{hibernate = Hibernate}) ->
+ Hibernate.
+
parse_actions(false, Debug, S, Actions) ->
parse_actions(true, Debug, S, Actions, #trans_opts{});
parse_actions(TransOpts, Debug, S, Actions) ->
@@ -1234,6 +1256,11 @@ parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
parse_actions(
StateCall, Debug, S, Actions,
TransOpts#trans_opts{postpone = true});
+ postpone ->
+ [error,
+ {bad_state_enter_action_from_state_function,Action},
+ ?STACKTRACE(),
+ Debug];
%%
{next_event,Type,Content} ->
parse_actions_next_event(
@@ -1286,7 +1313,8 @@ parse_actions_next_event(
next_events_r = [{Type,Content}|NextEventsR]});
_ ->
[error,
- {bad_action_from_state_function,{next_event,Type,Content}},
+ {bad_state_enter_action_from_state_function,
+ {next_event,Type,Content}},
?STACKTRACE(),
?not_sys_debug]
end;
@@ -1303,22 +1331,23 @@ parse_actions_next_event(
next_events_r = [{Type,Content}|NextEventsR]});
_ ->
[error,
- {bad_action_from_state_function,{next_event,Type,Content}},
+ {bad_state_enter_action_from_state_function,
+ {next_event,Type,Content}},
?STACKTRACE(),
Debug]
end.
parse_actions_timeout(
StateCall, Debug, S, Actions, TransOpts,
- {TimerType,Time,TimerMsg,TimerOpts} = AbsoluteTimeout) ->
+ {TimeoutType,Time,TimerMsg,TimerOpts} = AbsoluteTimeout) ->
%%
- case classify_timer(Time, listify(TimerOpts)) of
+ case classify_timeout(TimeoutType, Time, listify(TimerOpts)) of
absolute ->
parse_actions_timeout_add(
StateCall, Debug, S, Actions,
TransOpts, AbsoluteTimeout);
relative ->
- RelativeTimeout = {TimerType,Time,TimerMsg},
+ RelativeTimeout = {TimeoutType,Time,TimerMsg},
parse_actions_timeout_add(
StateCall, Debug, S, Actions,
TransOpts, RelativeTimeout);
@@ -1330,8 +1359,8 @@ parse_actions_timeout(
end;
parse_actions_timeout(
StateCall, Debug, S, Actions, TransOpts,
- {_,Time,_} = RelativeTimeout) ->
- case classify_timer(Time, []) of
+ {TimeoutType,Time,_} = RelativeTimeout) ->
+ case classify_timeout(TimeoutType, Time, []) of
relative ->
parse_actions_timeout_add(
StateCall, Debug, S, Actions,
@@ -1344,14 +1373,16 @@ parse_actions_timeout(
end;
parse_actions_timeout(
StateCall, Debug, S, Actions, TransOpts,
- Timeout) ->
- case classify_timer(Timeout, []) of
+ Time) ->
+ case classify_timeout(timeout, Time, []) of
relative ->
+ RelativeTimeout = {timeout,Time,Time},
parse_actions_timeout_add(
- StateCall, Debug, S, Actions, TransOpts, Timeout);
+ StateCall, Debug, S, Actions,
+ TransOpts, RelativeTimeout);
badarg ->
[error,
- {bad_action_from_state_function,Timeout},
+ {bad_action_from_state_function,Time},
?STACKTRACE(),
Debug]
end.
@@ -1637,10 +1668,15 @@ call_state_function(
%% -> absolute | relative | badarg
-classify_timer(Time, Opts) ->
- classify_timer(Time, Opts, false).
-%%
-classify_timer(Time, [], Abs) ->
+classify_timeout(TimeoutType, Time, Opts) ->
+ case timeout_event_type(TimeoutType) of
+ true ->
+ classify_time(false, Time, Opts);
+ false ->
+ badarg
+ end.
+
+classify_time(Abs, Time, []) ->
case Abs of
true when
is_integer(Time);
@@ -1653,9 +1689,9 @@ classify_timer(Time, [], Abs) ->
_ ->
badarg
end;
-classify_timer(Time, [{abs,Abs}|Opts], _) when is_boolean(Abs) ->
- classify_timer(Time, Opts, Abs);
-classify_timer(_, Opts, _) when is_list(Opts) ->
+classify_time(_, Time, [{abs,Abs}|Opts]) when is_boolean(Abs) ->
+ classify_time(Abs, Time, Opts);
+classify_time(_, _, Opts) when is_list(Opts) ->
badarg.
%% Stop and start timers as well as create timeout zero events
@@ -1686,15 +1722,7 @@ parse_timers(
{TimerType,Time,TimerMsg} ->
parse_timers(
TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- TimerType, Time, TimerMsg, []);
- 0 ->
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- timeout, zero, 0, []);
- Time ->
- parse_timers(
- TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents,
- timeout, Time, Time, [])
+ TimerType, Time, TimerMsg, [])
end.
parse_timers(
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 3f48fe1590..053233df9b 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -60,7 +60,8 @@ tcs(start) ->
tcs(stop) ->
[stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10];
tcs(abnormal) ->
- [abnormal1, abnormal1clean, abnormal1dirty, abnormal2];
+ [abnormal1, abnormal1clean, abnormal1dirty,
+ abnormal2, abnormal3, abnormal4];
tcs(sys) ->
[sys1, call_format_status,
error_format_status, terminate_crash_format,
@@ -524,6 +525,43 @@ abnormal2(Config) ->
process_flag(trap_exit, OldFl),
ok = verify_empty_msgq().
+%% Check that bad return actions makes the stm crash. Note that we must
+%% trap exit since we must link to get the real bad_return_ error
+abnormal3(Config) ->
+ OldFl = process_flag(trap_exit, true),
+ {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+
+ %% bad return value in the gen_statem loop
+ {{{bad_action_from_state_function,badaction},_},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, badaction), Reason),
+ receive
+ {'EXIT',Pid,{{bad_action_from_state_function,badaction},_}} -> ok
+ after 5000 ->
+ ct:fail(gen_statem_did_not_die)
+ end,
+
+ process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
+%% Check that bad timeout actions makes the stm crash. Note that we must
+%% trap exit since we must link to get the real bad_return_ error
+abnormal4(Config) ->
+ OldFl = process_flag(trap_exit, true),
+ {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+
+ %% bad return value in the gen_statem loop
+ BadTimeout = {badtimeout,4711,ouch},
+ {{{bad_action_from_state_function,BadTimeout},_},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, BadTimeout), Reason),
+ receive
+ {'EXIT',Pid,{{bad_action_from_state_function,BadTimeout},_}} -> ok
+ after 5000 ->
+ ct:fail(gen_statem_did_not_die)
+ end,
+
+ process_flag(trap_exit, OldFl),
+ ok = verify_empty_msgq().
+
shutdown(Config) ->
process_flag(trap_exit, true),
@@ -1806,10 +1844,12 @@ idle(cast, {connect,Pid}, Data) ->
idle({call,From}, connect, Data) ->
gen_statem:reply(From, accept),
{next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API
-idle(cast, badreturn, _Data) ->
- badreturn;
idle({call,_From}, badreturn, _Data) ->
badreturn;
+idle({call,_From}, badaction, Data) ->
+ {keep_state, Data, [badaction]};
+idle({call,_From}, {badtimeout,_,_} = BadTimeout, Data) ->
+ {keep_state, Data, BadTimeout};
idle({call,From}, {delayed_answer,T}, Data) ->
receive
after T ->