aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2018-09-19 15:19:54 +0200
committerRaimo Niskanen <[email protected]>2018-09-19 16:01:29 +0200
commit81f7ac4dae50339ce3549290c9081d23e87ce3c1 (patch)
treeccb8c57bb4addef20d442556f5d980ea17da9254 /lib
parent5dd0487617943e95da059abe1de65f99b6a32cb3 (diff)
parent4d636ebe55ddd557f62885b724076c1c146f62dd (diff)
downloadotp-81f7ac4dae50339ce3549290c9081d23e87ce3c1.tar.gz
otp-81f7ac4dae50339ce3549290c9081d23e87ce3c1.tar.bz2
otp-81f7ac4dae50339ce3549290c9081d23e87ce3c1.zip
Merge branch 'raimo/stdlib/gen_statem-cleanup'
OTP-14737 * raimo/stdlib/gen_statem-cleanup: Improve user's guide on time-outs Clean up and optimize code and doc
Diffstat (limited to 'lib')
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml228
-rw-r--r--lib/stdlib/src/gen_statem.erl261
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl73
3 files changed, 314 insertions, 248 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index eb0f7d24f0..dfecd235c9 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -167,7 +167,7 @@ erlang:'!' -----> Module:StateName/3
</p>
<marker id="state callback"/>
<p>
- The "<em>state callback</em>" for a specific
+ The <em>state callback</em> for a specific
<seealso marker="#type-state">state</seealso>
in a <c>gen_statem</c> is the callback function that is called
for all events in this state. It is selected depending on which
@@ -179,7 +179,7 @@ erlang:'!' -----> Module:StateName/3
When the
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>state_functions</c>, the state must be an atom and
- is used as the state callback name; see
+ is used as the <em>state callback</em> name; see
<seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>.
This co-locates all code for a specific state
in one function as the <c>gen_statem</c> engine
@@ -192,7 +192,7 @@ erlang:'!' -----> Module:StateName/3
When the
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>handle_event_function</c>, the state can be any term
- and the state callback name is
+ and the <em>state callback</em> name is
<seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
This makes it easy to branch depending on state or event as you desire.
Be careful about which events you handle in which
@@ -200,12 +200,36 @@ erlang:'!' -----> Module:StateName/3
forever creating an infinite busy loop.
</p>
<p>
- The <c>gen_statem</c> enqueues incoming events in order of arrival
- and presents these to the
- <seealso marker="#state callback">state callback</seealso>
- in that order. The state callback can postpone an event
- so it is not retried in the current state.
- After a state change the queue restarts with the postponed events.
+ When <c>gen_statem</c> receives a process message it is
+ converted into an event and the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ is called with the event as two arguments: type and content.
+ When the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ has processed the event it returns to <c>gen_statem</c>
+ which does a <em>state transition</em>.
+ If this <em>state transition</em> is to a different state,
+ that is: <c>NextState =/= State</c>, it is a <em>state change</em>.
+ </p>
+ <p>
+ The
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ may return
+ <seealso marker="#type-action"><em>transition actions</em></seealso>
+ for <c>gen_statem</c>
+ to execute during the <em>state transition</em>,
+ for example to reply to a
+ <seealso marker="#call/2"><c>gen_statem:call/2,3</c></seealso>.
+ </p>
+ <p>
+ One of the possible <em>transition actions</em>
+ is to postpone the current event.
+ Then it is not retried in the current state.
+ The <c>gen_statem</c> engine keeps a queue of events
+ divided into the postponed events
+ and the events still to process.
+ After a <em>state change</em> the queue restarts
+ with the postponed events.
</p>
<p>
The <c>gen_statem</c> event queue model is sufficient
@@ -215,13 +239,17 @@ erlang:'!' -----> Module:StateName/3
to entering a new receive statement.
</p>
<p>
- The <seealso marker="#state callback">state callback</seealso>
+ The
+ <seealso marker="#state callback"><em>state callback</em></seealso>
can insert events using the
- <seealso marker="#type-action"><c>action()</c></seealso>
+ <seealso marker="#type-action"><em>transition actions</em></seealso>
<c>next_event</c>
- and such an event is inserted as the next to present
- to the state callback. That is, as if it is
- the oldest incoming event. A dedicated
+ and such an event is inserted in the event queue
+ as the next to call the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ with.
+ That is, as if it is the oldest incoming event.
+ A dedicated
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
<c>internal</c> can be used for such events making them impossible
to mistake for external events.
@@ -236,24 +264,26 @@ erlang:'!' -----> Module:StateName/3
<p>
The <c>gen_statem</c> engine can automatically
make a specialized call to the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></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 an event at the state transition,
- and/or to use a dedicated state transition function,
+ Another way to do it is to explicitly insert an event
+ at the <em>state transition</em>,
+ and/or to use a dedicated <em>state transition</em> function,
but that is something you will have to remember
- at every state transition to the state(s) that need it.
+ at every <em>state transition</em> to the state(s) that need it.
</p>
<note>
<p>If you in <c>gen_statem</c>, for example, postpone
- an event in one state and then call another state callback
- of yours, you have not changed states and hence the postponed event
- is not retried, which is logical but can be confusing.
+ an event in one state and then call another <em>state callback</em>
+ of yours, you have not done a <em>state change</em>
+ and hence the postponed event is not retried,
+ which is logical but can be confusing.
</p>
</note>
<p>
- For the details of a state transition, see type
+ For the details of a <em>state transition</em>, see type
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>.
</p>
<p>
@@ -276,7 +306,8 @@ erlang:'!' -----> Module:StateName/3
The <c>gen_statem</c> process can go into hibernation; see
<seealso marker="proc_lib#hibernate/3"><c>proc_lib:hibernate/3</c></seealso>.
It is done when a
- <seealso marker="#state callback">state callback</seealso> or
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ or
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
specifies <c>hibernate</c> in the returned
<seealso marker="#type-action"><c>Actions</c></seealso>
@@ -551,7 +582,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>handle_event_function</c>,
the state can be any term.
- After a state change (<c>NextState =/= State</c>),
+ After a <em>state change</em> (<c>NextState =/= State</c>),
all postponed events are retried.
</p>
</desc>
@@ -564,7 +595,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
is <c>state_functions</c>,
the state must be of this type.
- After a state change (<c>NextState =/= State</c>),
+ After a <em>state change</em> (<c>NextState =/= State</c>),
all postponed events are retried.
</p>
</desc>
@@ -595,7 +626,7 @@ handle_event(_, _, State, Data) ->
</p>
<p>
<c>internal</c> events can only be generated by the
- state machine itself through the state transition action
+ state machine itself through the <em>transition action</em>
<seealso marker="#type-action"><c>next_event</c></seealso>.
</p>
</desc>
@@ -633,9 +664,9 @@ handle_event(_, _, State, Data) ->
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>
+ <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
and whether to do
- <seealso marker="#type-state_enter">state enter calls</seealso>,
+ <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
or not.
</p>
</desc>
@@ -684,13 +715,15 @@ handle_event(_, _, State, Data) ->
If
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
returns a list containing <c>state_enter</c>,
- the <c>gen_statem</c> engine will, at every state change,
+ the <c>gen_statem</c> engine will, at every <em>state change</em>,
call the
<seealso marker="#state callback">state callback</seealso>
with arguments <c>(enter, OldState, Data)</c>.
This may look like an event but is really a call
- performed after the previous state callback returned
- and before any event is delivered to the new state callback.
+ performed after the previous
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ returned and before any event is delivered to the new
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
See
<seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>
and
@@ -703,27 +736,27 @@ handle_event(_, _, State, Data) ->
<seealso marker="#type-state_callback_result">
<c>repeat_state_and_data</c>
</seealso>
- tuple from the state callback.
+ tuple from the <em>state callback</em>.
</p>
<p>
If
<seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>
- does not return such a list, no state enter calls are done.
+ does not return such a list, no <em>state enter calls</em> are done.
</p>
<p>
If
<seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>
should transform the state,
- it is regarded as a state rename and not a state change,
- which will not cause a state enter call.
+ it is regarded as a state rename and not a <em>state change</em>,
+ which will not cause a <em>state enter call</em>.
</p>
<p>
- Note that a state enter call <em>will</em> be done
+ Note that a <em>state enter call</em> <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 cannot happen for a subsequent state change,
- but will happen when repeating the state enter call.
+ actually is not a <em>state change</em>.
+ In this case <c>OldState =:= State</c>,
+ which can not happen for a subsequent state change,
+ but will happen when repeating the <em>state enter call</em>.
</p>
</desc>
</datatype>
@@ -733,8 +766,11 @@ handle_event(_, _, State, Data) ->
<p>
Transition options can be set by
<seealso marker="#type-action">actions</seealso>
- and modify the state transition.
- Here are the sequence of steps for a state transition:
+ and modify the <em>state transition</em>.
+ The <em>state transition</em> takes place when the
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ has processed an event and returns.
+ Here are the sequence of steps for a <em>state transition</em>:
</p>
<list type="ordered">
<item>
@@ -765,7 +801,7 @@ handle_event(_, _, State, Data) ->
returned by the state callback that caused the state entry.
</p>
<p>
- Should this state enter call return any of
+ Should this <em>state enter call</em> return any of
the mentioned <c>repeat_*</c> callback results
it is repeated again, with the updated <c>Data</c>.
</p>
@@ -787,7 +823,8 @@ handle_event(_, _, State, Data) ->
</item>
<item>
<p>
- If the state changes, the queue of incoming events
+ If this is a <em>state change</em>,
+ the queue of incoming events
is reset to start with the oldest postponed.
</p>
</item>
@@ -821,7 +858,7 @@ handle_event(_, _, State, Data) ->
if the event queue is empty.
</p>
<p>
- A state change cancels a
+ A <em>state change</em> cancels a
<seealso marker="#type-state_timeout"><c>state_timeout()</c></seealso>
and any new transition option of this type
belongs to the new state.
@@ -830,7 +867,7 @@ handle_event(_, _, State, Data) ->
<item>
<p>
If there are enqueued events the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
for the possibly new state
is called with the oldest enqueued event,
and we start again from the top of this list.
@@ -848,7 +885,7 @@ handle_event(_, _, State, Data) ->
the next incoming message awakens the <c>gen_statem</c>,
but if it is a system event it goes right back into hibernation.
When a new message arrives the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
is called with the corresponding event,
and we start again from the top of this sequence.
</p>
@@ -861,7 +898,7 @@ handle_event(_, _, State, Data) ->
<desc>
<p>
If <c>true</c>, postpones the current event and retries
- it when the state changes
+ it after a <em>state change</em>
(<c>NextState =/= State</c>).
</p>
</desc>
@@ -1021,9 +1058,9 @@ handle_event(_, _, State, Data) ->
<name name="action"/>
<desc>
<p>
- These state transition actions can be invoked by
+ These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
when it is called with an
<seealso marker="#type-event_type">event</seealso>,
from
@@ -1054,7 +1091,7 @@ handle_event(_, _, State, Data) ->
<c>transition_option()</c>
</seealso>
<seealso marker="#type-postpone"><c>postpone()</c></seealso>
- for this state transition.
+ for this <em>state transition</em>.
This action is ignored when returned from
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or given to
@@ -1093,9 +1130,9 @@ handle_event(_, _, State, Data) ->
<name name="enter_action"/>
<desc>
<p>
- These state transition actions can be invoked by
+ These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback">state callback</seealso>, from
+ <seealso marker="#state callback"><em>state callback</em></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>.
@@ -1119,7 +1156,7 @@ handle_event(_, _, State, Data) ->
Sets the
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>
<seealso marker="#type-hibernate"><c>hibernate()</c></seealso>
- for this state transition.
+ for this <em>state transition</em>.
</p>
</item>
</taglist>
@@ -1129,9 +1166,9 @@ handle_event(_, _, State, Data) ->
<name name="timeout_action"/>
<desc>
<p>
- These state transition actions can be invoked by
+ These <em>transition actions</em> can be invoked by
returning them from the
- <seealso marker="#state callback">state callback</seealso>, from
+ <seealso marker="#state callback"><em>state callback</em></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>.
@@ -1147,7 +1184,7 @@ handle_event(_, _, State, Data) ->
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>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
return value <c>{next_state,NextState,NewData,Time}</c>
allowed like for <c>gen_fsm</c>.
</p>
@@ -1193,9 +1230,9 @@ handle_event(_, _, State, Data) ->
<name name="reply_action"/>
<desc>
<p>
- This state transition action can be invoked by
+ This <em>transition action</em> can be invoked by
returning it from the
- <seealso marker="#state callback">state callback</seealso>, from
+ <seealso marker="#state callback"><em>state callback</em></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>.
@@ -1210,7 +1247,7 @@ handle_event(_, _, State, Data) ->
<c><anno>From</anno></c> must be the term from argument
<seealso marker="#type-event_type"><c>{call,<anno>From</anno>}</c></seealso>
in a call to a
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
</p>
<p>
Note that using this action from
@@ -1219,7 +1256,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>
would be weird on the border of witchcraft
since there has been no earlier call to a
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
in this server.
</p>
</desc>
@@ -1239,7 +1276,7 @@ handle_event(_, _, State, Data) ->
The <seealso marker="#type-action"><c>Actions</c></seealso>
are executed when entering the first
<seealso marker="#type-state">state</seealso> just as for a
- <seealso marker="#state callback">state callback</seealso>,
+ <seealso marker="#state callback"><em>state callback</em></seealso>,
except that the action <c>postpone</c> is forced to
<c>false</c> since there is no event to postpone.
</p>
@@ -1292,11 +1329,13 @@ handle_event(_, _, State, Data) ->
<tag><c>next_state</c></tag>
<item>
<p>
- The <c>gen_statem</c> does a state transition to
+ The <c>gen_statem</c> does a <em>state transition</em> 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>.
+ If <c><anno>NextState</anno> =/= CurrentState</c>
+ the <em>state transition</em> is a <em>state change</em>.
</p>
</item>
</taglist>
@@ -1318,54 +1357,33 @@ handle_event(_, _, State, Data) ->
<tag><c>keep_state</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state, or
- does a state transition to the current state if you like,
- sets <c><anno>NewData</anno></c>,
- and executes all <c><anno>Actions</anno></c>.
- This is the same as
+ The same as
<c>{next_state,CurrentState,<anno>NewData</anno>,<anno>Actions</anno>}</c>.
</p>
</item>
<tag><c>keep_state_and_data</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state or
- does a state transition to the current state if you like,
- keeps the current server data,
- and executes all <c><anno>Actions</anno></c>.
- This is the same as
- <c>{next_state,CurrentState,CurrentData,<anno>Actions</anno>}</c>.
+ The same as
+ <c>{keep_state,CurrentData,<anno>Actions</anno>}</c>.
</p>
</item>
<tag><c>repeat_state</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state, or
- does a state transition to the current state if you like,
- sets <c><anno>NewData</anno></c>,
- and executes all <c><anno>Actions</anno></c>.
If the <c>gen_statem</c> runs with
<seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
- the state enter call is repeated, see type
+ the <em>state enter call</em> is repeated, see type
<seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
- otherwise <c>repeat_state</c> is the same as
+ other than that <c>repeat_state</c> is the same as
<c>keep_state</c>.
</p>
</item>
<tag><c>repeat_state_and_data</c></tag>
<item>
<p>
- The <c>gen_statem</c> keeps the current state and data, or
- does a state transition to the current state if you like,
- and executes all <c><anno>Actions</anno></c>.
- This is the same as
+ The same as
<c>{repeat_state,CurrentData,<anno>Actions</anno>}</c>.
- If the <c>gen_statem</c> runs with
- <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
- the state enter call is repeated, see type
- <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
- otherwise <c>repeat_state_and_data</c> is the same as
- <c>keep_state_and_data</c>.
</p>
</item>
<tag><c>stop</c></tag>
@@ -1408,14 +1426,15 @@ handle_event(_, _, State, Data) ->
by sending a request
and waiting until its reply arrives.
The <c>gen_statem</c> calls the
- <seealso marker="#state callback">state callback</seealso> with
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ with
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
<c>{call,From}</c> and event content
<c><anno>Request</anno></c>.
</p>
<p>
A <c><anno>Reply</anno></c> is generated when a
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
returns with
<c>{reply,From,<anno>Reply</anno>}</c> as one
<seealso marker="#type-action"><c>action()</c></seealso>,
@@ -1484,7 +1503,8 @@ handle_event(_, _, State, Data) ->
ignoring if the destination node or <c>gen_statem</c>
does not exist.
The <c>gen_statem</c> calls the
- <seealso marker="#state callback">state callback</seealso> with
+ <seealso marker="#state callback"><em>state callback</em></seealso>
+ with
<seealso marker="#type-event_type"><c>event_type()</c></seealso>
<c>cast</c> and event content
<c><anno>Msg</anno></c>.
@@ -1598,18 +1618,18 @@ handle_event(_, _, State, Data) ->
<seealso marker="#call/2"><c>call/2</c></seealso>
when the reply cannot be defined in
the return value of a
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
</p>
<p>
<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
- <seealso marker="#state callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
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 callback">state callback</seealso>.
+ <seealso marker="#state callback"><em>state callback</em></seealso>.
</p>
<note>
<p>
@@ -1826,7 +1846,7 @@ handle_event(_, _, State, Data) ->
for efficiency reasons, so this function is only called
once after server start and after code change,
but before the first
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
in the current code version is called.
More occasions may be added in future versions
of <c>gen_statem</c>.
@@ -1883,7 +1903,7 @@ handle_event(_, _, State, Data) ->
<p>
This callback is optional, so callback modules need not export it.
If a release upgrade/downgrade with
- <c>Change={advanced,Extra}</c>
+ <c>Change = {advanced,Extra}</c>
specified in the <c>.appup</c> file is made
when <c>code_change/4</c> is not implemented
the process will crash with exit reason <c>undef</c>.
@@ -1893,7 +1913,7 @@ handle_event(_, _, State, Data) ->
This function is called by a <c>gen_statem</c> when it is to
update its internal state during a release upgrade/downgrade,
that is, when the instruction <c>{update,Module,Change,...}</c>,
- where <c>Change={advanced,Extra}</c>, is specified in the
+ where <c>Change = {advanced,Extra}</c>, is specified in the
<seealso marker="sasl:appup"><c>appup</c></seealso>
file. For more information, see
<seealso marker="doc/design_principles:release_handling#instr">OTP Design Principles</seealso>.
@@ -1933,13 +1953,13 @@ handle_event(_, _, State, Data) ->
<p>
Also note when upgrading a <c>gen_statem</c>,
this function and hence
- the <c>Change={advanced,Extra}</c> parameter in the
+ the <c>Change = {advanced,Extra}</c> parameter in the
<seealso marker="sasl:appup"><c>appup</c></seealso> file
is not only needed to update the internal state
or to act on the <c>Extra</c> argument.
It is also needed if an upgrade or downgrade should change
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>,
- or else the callback mode after the code change
+ or else the <em>callback mode</em> after the code change
will not be honoured,
most probably causing a server crash.
</p>
@@ -2148,7 +2168,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
<seealso marker="#type-event_type"><c>{call,From}</c></seealso>,
the caller waits for a reply. The reply can be sent
from this or from any other
- <seealso marker="#state callback">state callback</seealso>
+ <seealso marker="#state callback"><em>state callback</em></seealso>
by returning with <c>{reply,From,Reply}</c> in
<seealso marker="#type-action"><c>Actions</c></seealso>, in
<seealso marker="#type-reply_action"><c>Replies</c></seealso>,
@@ -2173,9 +2193,9 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre>
</p>
<p>
When the <c>gen_statem</c> runs with
- <seealso marker="#type-state_enter">state enter calls</seealso>,
+ <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
these functions are also called with arguments
- <c>(enter, OldState, ...)</c> whenever the state changes.
+ <c>(enter, OldState, ...)</c> during every <em>state change</em>.
In this case there are some restrictions on the
<seealso marker="#type-enter_action">actions</seealso>
that may be returned:
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index faa43fbc1e..24b268cd38 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -330,6 +330,7 @@
%% Type validation functions
+%% - return true if the value is of the type, false otherwise
-compile(
{inline,
[callback_mode/1, state_enter/1,
@@ -1277,7 +1278,7 @@ parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) ->
end.
parse_actions_reply(
- StateCall, ?not_sys_debug, S, Actions, TransOpts,
+ StateCall, ?not_sys_debug = Debug, S, Actions, TransOpts,
From, Reply) ->
%%
case from(From) of
@@ -1287,8 +1288,7 @@ parse_actions_reply(
false ->
[error,
{bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(),
- ?not_sys_debug]
+ ?STACKTRACE(), Debug]
end;
parse_actions_reply(
StateCall, Debug, #state{name = Name, state = State} = S,
@@ -1302,12 +1302,11 @@ parse_actions_reply(
false ->
[error,
{bad_action_from_state_function,{reply,From,Reply}},
- ?STACKTRACE(),
- Debug]
+ ?STACKTRACE(), Debug]
end.
parse_actions_next_event(
- StateCall, ?not_sys_debug, S,
+ StateCall, ?not_sys_debug = Debug, S,
Actions, TransOpts, Type, Content) ->
case event_type(Type) of
true when StateCall ->
@@ -1320,8 +1319,7 @@ parse_actions_next_event(
[error,
{bad_state_enter_action_from_state_function,
{next_event,Type,Content}},
- ?STACKTRACE(),
- ?not_sys_debug]
+ ?STACKTRACE(), Debug]
end;
parse_actions_next_event(
StateCall, Debug, #state{name = Name, state = State} = S,
@@ -1403,13 +1401,13 @@ parse_actions_timeout_add(
loop_event_done(
Parent, ?not_sys_debug,
#state{postponed = P} = S,
+%% #state{postponed = will_not_happen = P} = S,
Events, Event, NextState, NewData,
#trans_opts{
postpone = Postpone, hibernate = Hibernate,
- timeouts_r = [], next_events_r = []}) ->
+ timeouts_r = [], next_events_r = NextEventsR}) ->
%%
- %% Optimize the simple cases
- %% i.e no timer changes, no inserted events and no debug,
+ %% Optimize the simple cases i.e no debug and no timer changes,
%% by duplicate stripped down code
%%
%% Fast path
@@ -1417,14 +1415,12 @@ loop_event_done(
case Postpone of
true ->
loop_event_done_fast(
- Parent, Hibernate,
- S,
- Events, [Event|P], NextState, NewData);
+ Parent, Hibernate, S,
+ Events, [Event|P], NextState, NewData, NextEventsR);
false ->
loop_event_done_fast(
- Parent, Hibernate,
- S,
- Events, P, NextState, NewData)
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR)
end;
loop_event_done(
Parent, Debug_0,
@@ -1456,26 +1452,23 @@ loop_event_done(
{S#state.name,State},
{consume,Event_0,NextState})|P_0]
end,
- {Events_2,P_2,Timers_2} =
- %% Move all postponed events to queue,
- %% cancel the event timer,
- %% and cancel the state timeout if the state changes
- if
- NextState =:= State ->
- {Events_0,P_1,
+ {Events_2,P_2,
+ Timers_2} =
+ %% Cancel the event timeout
+ if
+ NextState =:= State ->
+ {Events_0,P_1,
cancel_timer_by_type(
timeout, {TimerTypes_0,CancelTimers_0})};
- true ->
- {lists:reverse(P_1, Events_0),
- [],
- cancel_timer_by_type(
- state_timeout,
+ true ->
+ %% Move all postponed events to queue
+ %% and cancel the state timeout
+ {lists:reverse(P_1, Events_0),[],
+ cancel_timer_by_type(
+ state_timeout,
cancel_timer_by_type(
timeout, {TimerTypes_0,CancelTimers_0}))}
- %% The state timer is removed from TimerTypes
- %% but remains in TimerRefs until we get
- %% the cancel_timer msg
- end,
+ end,
{TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} =
%% Stop and start timers
parse_timers(TimerRefs_0, Timers_2, TimeoutsR),
@@ -1495,114 +1488,144 @@ loop_event_done(
hibernate = Hibernate},
lists:reverse(Events_4R)).
+loop_event_done(Parent, Debug, S, Q) ->
+%% io:format(
+%% "loop_event_done:~n"
+%% " state = ~p, data = ~p, postponed = ~p~n "
+%% " timer_refs = ~p, timer_types = ~p, cancel_timers = ~p.~n"
+%% " Q = ~p.~n",
+%% [S#state.state,S#state.data,S#state.postponed,
+%% S#state.timer_refs,S#state.timer_types,S#state.cancel_timers,
+%% Q]),
+ case Q of
+ [] ->
+ %% Get a new event
+ loop(Parent, Debug, S);
+ [{Type,Content}|Events] ->
+ %% Loop until out of enqueued events
+ loop_event(Parent, Debug, S, Events, Type, Content)
+ end.
+
+
%% Fast path
%%
+%% Cancel event timer and state timer only if running
loop_event_done_fast(
Parent, Hibernate,
#state{
state = NextState,
- timer_types = #{timeout := _} = TimerTypes,
+ timer_types = TimerTypes,
cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% Same state, event timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- Events, P, NextState, NewData,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers}));
-loop_event_done_fast(
- Parent, Hibernate,
- #state{state = NextState} = S,
- Events, P, NextState, NewData) ->
- %%
+ Events, P, NextState, NewData, NextEventsR) ->
%% Same state
- %%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- data = NewData,
- postponed = P,
- hibernate = Hibernate},
- Events);
-loop_event_done_fast(
- Parent, Hibernate,
- #state{
- timer_types = #{timeout := _} = TimerTypes,
- cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% State change, event timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
+ case TimerTypes of
+ #{timeout := _} ->
+ %% Event timeout active
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ cancel_timer_by_type(
+ timeout, {TimerTypes,CancelTimers}));
+ _ ->
+ %% No event timeout active
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ {TimerTypes,CancelTimers})
+ end;
loop_event_done_fast(
Parent, Hibernate,
#state{
- timer_types = #{state_timeout := _} = TimerTypes,
+ timer_types = TimerTypes,
cancel_timers = CancelTimers} = S,
- Events, P, NextState, NewData) ->
- %%
- %% State change, state timeout active
- %%
- loop_event_done_fast(
- Parent, Hibernate, S,
- lists:reverse(P, Events), [], NextState, NewData,
- cancel_timer_by_type(
- state_timeout,
- cancel_timer_by_type(
- timeout, {TimerTypes,CancelTimers})));
+ Events, P, NextState, NewData, NextEventsR) ->
+ %% State change
+ case TimerTypes of
+ #{timeout := _} ->
+ %% Event timeout active
+ %% - cancel state_timeout too since it is faster than inspecting
+ loop_event_done_fast(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ cancel_timer_by_type(
+ state_timeout,
+ cancel_timer_by_type(
+ timeout, {TimerTypes,CancelTimers})));
+ #{state_timeout := _} ->
+ %% State_timeout active but not event timeout
+ loop_event_done_fast(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ cancel_timer_by_type(
+ state_timeout, {TimerTypes,CancelTimers}));
+ _ ->
+ %% No event nor state_timeout active
+ loop_event_done_fast(
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR,
+ {TimerTypes,CancelTimers})
+ end.
+%%
+%% Retry postponed events
loop_event_done_fast(
- Parent, Hibernate,
- #state{} = S,
- Events, P, NextState, NewData) ->
- %%
- %% State change, no timeout to automatically cancel
- %%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- state = NextState,
- data = NewData,
- postponed = [],
- hibernate = Hibernate},
- lists:reverse(P, Events)).
+ Parent, Hibernate, S,
+ Events, P, NextState, NewData, NextEventsR, TimerTypes_CancelTimers) ->
+ case P of
+ %% Handle 0..2 postponed events without list reversal since
+ %% that will move out all live registers and back again
+ [] ->
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ Events, [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers);
+ [E] ->
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ [E|Events], [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers);
+ [E1,E2] ->
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ [E2,E1|Events], [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers);
+ _ ->
+ %% A bit slower path
+ loop_event_done_fast_2(
+ Parent, Hibernate, S,
+ lists:reverse(P, Events), [], NextState, NewData, NextEventsR,
+ TimerTypes_CancelTimers)
+ end.
%%
%% Fast path
%%
-loop_event_done_fast(
+loop_event_done_fast_2(
Parent, Hibernate, S,
- Events, P, NextState, NewData,
+ Events, P, NextState, NewData, NextEventsR,
{TimerTypes,CancelTimers}) ->
%%
- loop_event_done(
- Parent, ?not_sys_debug,
- S#state{
- state = NextState,
- data = NewData,
- postponed = P,
- timer_types = TimerTypes,
- cancel_timers = CancelTimers,
- hibernate = Hibernate},
- Events).
-
-loop_event_done(Parent, Debug, S, Q) ->
- case Q of
+ NewS =
+ S#state{
+ state = NextState,
+ data = NewData,
+ postponed = P,
+ timer_types = TimerTypes,
+ cancel_timers = CancelTimers,
+ hibernate = Hibernate},
+ case NextEventsR of
+ %% Handle 0..2 next events without list reversal since
+ %% that will move out all live registers and back again
[] ->
- %% Get a new event
- loop(Parent, Debug, S);
- [{Type,Content}|Events] ->
- %% Loop until out of enqueued events
- loop_event(Parent, Debug, S, Events, Type, Content)
+ loop_event_done(Parent, ?not_sys_debug, NewS, Events);
+ [E] ->
+ loop_event_done(Parent, ?not_sys_debug, NewS, [E|Events]);
+ [E2,E1] ->
+ loop_event_done(Parent, ?not_sys_debug, NewS, [E1,E2|Events]);
+ _ ->
+ %% A bit slower path
+ loop_event_done(
+ Parent, ?not_sys_debug, NewS, lists:reverse(NextEventsR, Events))
end.
-
%%---------------------------------------------------------------------------
%% Server loop helpers
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 053233df9b..017939fdd6 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -121,7 +121,8 @@ end_per_testcase(_CaseName, Config) ->
start1(Config) ->
%%OldFl = process_flag(trap_exit, true),
- {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []),
+ {ok,Pid0} =
+ gen_statem:start_link(?MODULE, start_arg(Config, []), [{debug,[trace]}]),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stop_it(Pid0),
@@ -135,7 +136,8 @@ start1(Config) ->
%% anonymous w. shutdown
start2(Config) ->
%% Dont link when shutdown
- {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []),
+ {ok,Pid0} =
+ gen_statem:start(?MODULE, start_arg(Config, []), []),
ok = do_func_test(Pid0),
ok = do_sync_func_test(Pid0),
stopped = gen_statem:call(Pid0, {stop,shutdown}),
@@ -641,51 +643,72 @@ state_enter(_Config) ->
end,
start =>
fun (enter, Prev, N) ->
- Self ! {enter,start,Prev,N},
+ Self ! {N,enter,start,Prev},
{keep_state,N + 1};
(internal, Prev, N) ->
- Self ! {internal,start,Prev,N},
+ Self ! {N,internal,start,Prev},
{keep_state,N + 1};
+ (timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {timeout,M}}};
({call,From}, repeat, N) ->
{repeat_state,N + 1,
- [{reply,From,{repeat,start,N}}]};
+ [{reply,From,{N,repeat,start}}]};
({call,From}, echo, N) ->
{next_state,wait,N + 1,
- {reply,From,{echo,start,N}}};
+ [{reply,From,{N,echo,start}},{timeout,0,N}]};
({call,From}, {stop,Reason}, N) ->
{stop_and_reply,Reason,
- [{reply,From,{stop,N}}],N + 1}
+ [{reply,From,{N,stop}}],N + 1}
end,
wait =>
fun (enter, Prev, N) when N < 5 ->
{repeat_state,N + 1,
- {reply,{Self,N},{enter,Prev}}};
+ [{reply,{Self,N},{enter,Prev}},
+ {timeout,0,N},
+ {state_timeout,0,N}]};
(enter, Prev, N) ->
- Self ! {enter,wait,Prev,N},
- {keep_state,N + 1};
+ Self ! {N,enter,wait,Prev},
+ {keep_state,N + 1,
+ [{timeout,0,N},
+ {state_timeout,0,N}]};
+ (timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {timeout,M}}};
+ (state_timeout, M, N) ->
+ {keep_state, N + 1,
+ {reply, {Self,N}, {state_timeout,M}}};
({call,From}, repeat, N) ->
{repeat_state_and_data,
- [{reply,From,{repeat,wait,N}}]};
+ [{reply,From,{N,repeat,wait}},
+ {timeout,0,N}]};
({call,From}, echo, N) ->
{next_state,start,N + 1,
[{next_event,internal,wait},
- {reply,From,{echo,wait,N}}]}
+ {reply,From,{N,echo,wait}}]}
end},
{ok,STM} =
gen_statem:start_link(
- ?MODULE, {map_statem,Machine,[state_enter]}, []),
-
- [{enter,start,start,1}] = flush(),
- {echo,start,2} = gen_statem:call(STM, echo),
- [{3,{enter,start}},{4,{enter,start}},{enter,wait,start,5}] = flush(),
- {wait,[6|_]} = sys:get_state(STM),
- {repeat,wait,6} = gen_statem:call(STM, repeat),
- [{enter,wait,wait,6}] = flush(),
- {echo,wait,7} = gen_statem:call(STM, echo),
- [{enter,start,wait,8},{internal,start,wait,9}] = flush(),
- {repeat,start,10} = gen_statem:call(STM, repeat),
- [{enter,start,start,11}] = flush(),
- {stop,12} = gen_statem:call(STM, {stop,bye}),
+ ?MODULE, {map_statem,Machine,[state_enter]}, [{debug,[trace]}]),
+
+ [{1,enter,start,start}] = flush(),
+ {2,echo,start} = gen_statem:call(STM, echo),
+ [{3,{enter,start}},
+ {4,{enter,start}},
+ {5,enter,wait,start},
+ {6,{timeout,5}},
+ {7,{state_timeout,5}}] = flush(),
+ {wait,[8|_]} = sys:get_state(STM),
+ {8,repeat,wait} = gen_statem:call(STM, repeat),
+ [{8,enter,wait,wait},
+ {9,{timeout,8}},
+ {10,{state_timeout,8}}] = flush(),
+ {11,echo,wait} = gen_statem:call(STM, echo),
+ [{12,enter,start,wait},
+ {13,internal,start,wait}] = flush(),
+ {14,repeat,start} = gen_statem:call(STM, repeat),
+ [{15,enter,start,start}] = flush(),
+ {16,stop} = gen_statem:call(STM, {stop,bye}),
[{'EXIT',STM,bye}] = flush(),
{noproc,_} =