aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml298
-rw-r--r--lib/stdlib/src/gen_statem.erl439
2 files changed, 436 insertions, 301 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 8934d912c6..1e41d616e9 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -143,8 +143,8 @@ erlang:'!' -----> Module:StateName/4
<p>The
<seealso marker="#state_function">state function</seealso>
can insert events using the
- <seealso marker="#type-transition_action">
- <c>transition_action()</c> <c>next_event</c>
+ <seealso marker="#type-action">
+ <c>action()</c> <c>next_event</c>
</seealso>
and such an event is inserted as the next to present
to the state function. That is: as if it is
@@ -158,12 +158,19 @@ erlang:'!' -----> Module:StateName/4
<p>Inserting an event replaces the trick of calling your own
state handling functions that you often would have to
resort to in e.g <seealso marker="gen_fsm">gen_fsm</seealso>
- to force processing a faked event before others.
+ to force processing an inserted event before others.
If you for example in <c>gen_statem</c> postpone an event
in one state and then call some other state function of yours,
you have not changed states and hence the postponed event
will not be retried, which is logical but might be confusing.
</p>
+ <p>
+ See the type
+ <seealso marker="#type-transition_option">
+ <c>transition_option()</c>.
+ </seealso>
+ for the details of a state transition.
+ </p>
<p>A <c>gen_statem</c> handles system messages as documented in
<seealso marker="sys">sys</seealso>.
The <seealso marker="sys">sys</seealso> module
@@ -184,7 +191,7 @@ erlang:'!' -----> Module:StateName/4
<seealso marker="#state_function">state function</seealso> or
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
specifies <c>'hibernate'</c> in the returned
- <seealso marker="#type-transition_op"><c>Ops</c></seealso>
+ <seealso marker="#type-action"><c>Actions</c></seealso>
list. This might be useful if the server is expected to be idle
for a long time. However use this feature with care
since hibernation implies at least two garbage collections
@@ -281,8 +288,8 @@ erlang:'!' -----> Module:StateName/4
<name name="caller" />
<desc>
<p>Destination to use when replying through for example the
- <seealso marker="#type-transition_op">
- transition_op() {reply,Caller,Reply}
+ <seealso marker="#type-action">
+ action() {reply,Caller,Reply}
</seealso>
to a process that has called the <c>gen_statem</c> server using
<seealso marker="#call/2">call/2</seealso>.
@@ -344,10 +351,11 @@ erlang:'!' -----> Module:StateName/4
<desc>
<p>A <c>fun()</c> of arity 2 that takes an event
and returns a boolean.
- When used in <c>{remove_event,RemoveEventPredicate}</c> from
- <seealso marker="#type-transition_op">transition_op()</seealso>.
- The event for which the predicate returns <c>true</c> will
- be removed.
+ When used in the
+ <seealso marker="#type-action">action()</seealso>
+ <c>{remove_event,RemoveEventPredicate}</c>,
+ the event for which the predicate returns <c>true</c>
+ will be removed.
</p>
<p>
The predicate may <em>not</em> use a throw exception
@@ -384,40 +392,45 @@ erlang:'!' -----> Module:StateName/4
</desc>
</datatype>
<datatype>
- <name name="transition_op" />
+ <name name="transition_option" />
<desc>
- <p>Either a
- <seealso marker="#type-transition_option">
- <c>transition_option()</c>
- </seealso> of which the last occurence
- in the containing list takes precedence, or a
- <seealso marker="#type-transition_action">
- <c>transition_action()</c>
- </seealso> performed in the order of the containing list.
- </p>
- <p>These may be returned from the
- <seealso marker="#state_function">state function</seealso>,
- from <seealso marker="#Module:init/1">Module:init/1</seealso>
- or given to
- <seealso marker="#enter_loop/6">enter_loop/6,7</seealso>.
+ <p>
+ Transition options may be set by
+ <seealso marker="#type-action">actions</seealso>
+ and they modify how the state transition is done:
</p>
- <p>The processing order for a state change is:</p>
<list type="ordered">
- <item>If the option <c>postpone</c> is <c>true</c>
- the current event is postponed.
+ <item>All
+ <seealso marker="#type-action"><c>actions</c></seealso>
+ are processed in order of appearance.
</item>
- <item>If the state changes the queue of incoming events
- is reset to start with the oldest postponed.
+ <item>
+ If the
+ <seealso marker="#type-postpone">
+ <c>transition_option()</c>
+ </seealso>
+ <seealso marker="#type-postpone"><c>postpone</c></seealso>
+ is <c>true</c> the current event is postponed.
</item>
- <item>All actions are processed in order of appearance.
+ <item>
+ If the state changes the queue of incoming events
+ is reset to start with the oldest postponed.
</item>
- <item>The <c>timeout</c> option is processed if present,
- so a state timer may be started or a timeout zero event
+ <item>
+ If the
+ <seealso marker="#type-postpone">
+ <c>transition_option()</c>
+ </seealso>
+ <seealso marker="#type-state_timeout">
+ <c>timeout</c>
+ </seealso>
+ is set a state timer may be started or a timeout zero event
may be enqueued as the newest incoming.
</item>
- <item>The (possibly new)
- <seealso marker="#state_function">state function</seealso>
- is called with the oldest enqueued event if there is any,
+ <item>
+ The (possibly new)
+ <seealso marker="#state_function">state function</seealso>
+ is called with the oldest enqueued event if there is any,
otherwise the <c>gen_statem</c> goes into <c>receive</c>
or hibernation (if the option <c>hibernate</c> is <c>true</c>)
to wait for the next message. In hibernation the next
@@ -429,91 +442,145 @@ erlang:'!' -----> Module:StateName/4
</desc>
</datatype>
<datatype>
- <name name="transition_option" />
+ <name name="postpone" />
+ <desc>
+ <p>
+ If <c>true</c> postpone the current event and retry
+ it when the state changes that is:
+ <c>NewState =/= State</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="hibernate" />
+ <desc>
+ <p>
+ If <c>true</c> hibernate the <c>gen_statem</c>
+ by calling
+ <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 the <c>hibernate</c>
+ is ignored as if an event just arrived and awakened
+ the <c>gen_statem</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="state_timeout" />
<desc>
- <p>If multiple state options of the same kind are present
- in the containing list these are set in the list order
- and the last value is kept.
+ <p>
+ Generate an event of
+ <seealso marker="#type-event_type">type <c>timeout</c></seealso>
+ after this time (in milliseconds) unless some other
+ event arrives in which case this timeout is cancelled.
+ Note that a retried or inserted event
+ counts just like a new in this respect.
+ If the value is <c>infinity</c> no timer is started.
+ If it is <c>0</c> the timeout event
+ is immediately enqueued as the newest received.
+ Also note that it is not possible nor needed
+ to cancel this timeout using the
+ <seealso marker="#type-action">
+ <c>action() cancel_timer</c>.
+ </seealso>
+ since this timeout is cancelled automatically by any other event.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="action" />
+ <desc>
+ <p>These state transition actions may be invoked by
+ returning them from the
+ <seealso marker="#state_function">state function</seealso>,
+ from <seealso marker="#Module:init/1">Module:init/1</seealso>
+ or by giving them to
+ <seealso marker="#enter_loop/6">enter_loop/6,7</seealso>.
+ </p>
+ <p>Actions are executed in the containing list order.
+ The order matters for some actions such as <c>next_event</c>
+ and <c>reply_action()</c>. The order can in peculiar cases
+ matter for <c>remove_event</c> with
+ <c><anno>EventPredicate</anno></c> versus other
+ event removal actions.
+ </p>
+ <p>
+ The order also matters for actions that set
+ <seealso marker="#type-transition_option">
+ transition options
+ </seealso>
+ since setting an option overrides any previous
+ of the same kind, so the last in the containing list wins.
</p>
<taglist>
<tag><c>postpone</c></tag>
- <item>If <c><anno>Postpone</anno> =:= true</c>
- or plain <c>postpone</c> postpone the current event
- to be retried after a state change.
- This option is ignored when returned from
+ <item>
+ Set the
+ <seealso marker="#type-transition_option">
+ <c>transition_option() postpone</c>
+ </seealso>
+ for this state transition.
+ This action is ignored when returned from
<seealso marker="#Module:init/1">Module:init/1</seealso>
or given to
<seealso marker="#enter_loop/5">enter_loop/5,6</seealso>
since there is no event to postpone in those cases.
</item>
<tag><c>hibernate</c></tag>
- <item>If <c><anno>Hibernate</anno> =:= true</c>
- or plain <c>hibernate</c> hibernate the <c>gen_statem</c>
- by calling
- <seealso marker="proc_lib#hibernate/3">
- <c>proc_lib:hibernate/3</c>
- </seealso> before <c>receive</c> to wait for a new event.
- If there are enqueued events the <c>hibernate</c> action
- is ignored as if an event just arrived and awakened
- the <c>gen_statem</c>.
+ <item>
+ Set the
+ <seealso marker="#type-transition_option">
+ <c>transition_option() hibernate</c>
+ </seealso>
+ for this state transition.
</item>
- <tag><c>timeout</c>
- </tag>
- <item>Generate an event of
- <seealso marker="#type-event_type">type <c>timeout</c></seealso>
- after <c><anno>Time</anno></c> milliseconds unless some other
- event is received before that time. Note that a retried
- or inserted event counts just like a new in this respect.
- If <c>Time =:= infinity</c> no timer is started.
- If <c>Time =:= 0</c> the timeout event
- is immediately enqueued as the newest received.
- Also note that it is not possible nor needed
- to cancel this timeout using the
- <seealso marker="#type-transition_action">
- <c>transition_action()</c>
- </seealso> <c>cancel_timer</c>.
- This timeout is cancelled automatically by any other event.
+ <tag><c>timeout</c></tag>
+ <item>
+ Set the
+ <seealso marker="#type-transition_option">
+ <c>transition_option() timeout</c>
+ </seealso>
+ to <c><anno>Time</anno></c> with the
+ <c>EventContent</c> as <c><anno>Msg</anno></c>
+ for the next state.
</item>
- </taglist>
- </desc>
- </datatype>
- <datatype>
- <name name="transition_action" />
- <desc>
- <p>The state transition actions are executed
- in the containing list order. This matters
- for <c>next_event</c> where the last such in the list
- will become the next event to process by
- the current state function. Regarding the other actions
- it is only for <c>remove_event</c> with
- <c><anno>EventPredicate</anno></c>
- and for <c>reply_action()</c> that the order may matter.
- </p>
- <taglist>
<tag><c>reply_action()</c></tag>
<item>Reply to a caller.</item>
<tag><c>next_event</c></tag>
- <item>Insert the given <c><anno>EventType</anno></c>
+ <item>
+ Insert the given <c><anno>EventType</anno></c>
and <c><anno>EventContent</anno></c> as the next to process.
This will bypass any events in the process mailbox as well
as any other queued events.
An event of type
<seealso marker="#type-event_type">
<c>internal</c>
- </seealso> should be used when you want to reliably distinguish
+ </seealso>
+ should be used when you want to reliably distinguish
an event inserted this way from any external event.
+ If there are multiple <c>next_event</c> actions
+ in the containing list they are buffered and all are
+ inserted so the first in the list will be the
+ first to process.
</item>
<tag><c>remove_event</c></tag>
- <item>Remove the oldest queued event
+ <item>
+ Remove the oldest queued event
that matches equal to <c><anno>EventType</anno></c>
and <c><anno>EventContent</anno></c> or for which
- <c><anno>EventPredicate</anno></c> returns <c>true</c>.
+ <c><anno>EventPredicate</anno></c>
+ returns <c>true</c>.
</item>
<tag><c>cancel_timer</c></tag>
- <item>Cancel the timer by calling
+ <item>
+ Cancel the timer by calling
<seealso marker="erts:erlang#cancel_timer/2">
<c>erlang:cancel_timer/2</c>
- </seealso> with <c><anno>TimerRef</anno></c>,
+ </seealso>
+ with <c><anno>TimerRef</anno></c>,
clean the process message queue from any late timeout message,
and removes any late timeout message
from the <c>gen_statem</c> event queue using
@@ -523,16 +590,20 @@ erlang:'!' -----> Module:StateName/4
the primitives mentioned above.
</item>
<tag><c>demonitor</c></tag>
- <item>Like <c>cancel_timer</c> above but for
+ <item>
+ Like <c>cancel_timer</c> above but for
<seealso marker="erts:erlang#demonitor/2">
<c>demonitor/2</c>
- </seealso> with <c><anno>MonitorRef</anno></c>.
+ </seealso>
+ with <c><anno>MonitorRef</anno></c>.
</item>
<tag><c>unlink</c></tag>
- <item>Like <c>{cancel_timer,_}</c> above but for
+ <item>
+ Like <c>{cancel_timer,_}</c> above but for
<seealso marker="erts:erlang#unlink/1">
<c>unlink/1</c>
- </seealso> with <c><anno>Id</anno></c>.
+ </seealso>
+ with <c><anno>Id</anno></c>.
</item>
</taglist>
</desc>
@@ -574,19 +645,19 @@ erlang:'!' -----> Module:StateName/4
<c><anno>NewState</anno></c>
(which may be the same as the current state),
set <c><anno>NewData</anno></c>
- and execute all <c><anno>Ops</anno></c>
+ and execute all <c><anno>Actions</anno></c>
</item>
<tag><c>keep_state</c></tag>
<item>The <c>gen_statem</c> will keep the current state, or
do a state transition to the current state if you like,
set <c><anno>NewData</anno></c>
- and execute all <c><anno>Ops</anno></c>
+ and execute all <c><anno>Actions</anno></c>
</item>
<tag><c>keep_state_and_data</c></tag>
<item>The <c>gen_statem</c> will keep the current state, or
do a state transition to the current state if you like,
keep the current server data,
- and execute all <c><anno>Ops</anno></c>
+ and execute all <c><anno>Actions</anno></c>
</item>
</taglist>
</desc>
@@ -776,8 +847,8 @@ erlang:'!' -----> Module:StateName/4
<seealso marker="#state_function">state function</seealso>
returns with
<c>{reply,Caller,<anno>Reply</anno>}</c> as one
- <seealso marker="#type-transition_op">
- <c>transition_op()</c>
+ <seealso marker="#type-action">
+ <c>action()</c>
</seealso>,
and that <c><anno>Reply</anno></c> becomes the return value
of this function.
@@ -870,20 +941,20 @@ erlang:'!' -----> Module:StateName/4
<name name="enter_loop" arity="6" />
<fsummary>Enter the <c>gen_statem</c> receive loop</fsummary>
<desc>
- <p>If <c><anno>Server_or_Ops</anno></c> is a <c>list()</c>
+ <p>If <c><anno>Server_or_Actions</anno></c> is a <c>list()</c>
the same as
<seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso>
except that no
<seealso marker="#type-server_name">
<c>server_name()</c>
</seealso> must have been registered and
- <c>Ops = <anno>Server_or_Ops</anno></c>.
+ <c>Actions = <anno>Server_or_Actions</anno></c>.
</p>
<p>Otherwise the same as
<seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso>
with
- <c>Server = <anno>Server_or_Ops</anno></c> and
- <c>Ops = []</c>.
+ <c>Server = <anno>Server_or_Actions</anno></c> and
+ <c>Actions = []</c>.
</p>
</desc>
</func>
@@ -917,7 +988,7 @@ erlang:'!' -----> Module:StateName/4
</seealso> name must have been registered accordingly
<em>before</em> this function is called.</p>
<p><c><anno>CallbackMode</anno></c>, <c><anno>State</anno></c>,
- <c><anno>Data</anno></c> and <c><anno>Ops</anno></c>
+ <c><anno>Data</anno></c> and <c><anno>Actions</anno></c>
have the same meanings as in the return value of
<seealso marker="#Module:init/1">Module:init/1</seealso>.
Also, the callback module <c><anno>Module</anno></c>
@@ -952,7 +1023,7 @@ erlang:'!' -----> Module:StateName/4
<type>
<v>Args = term()</v>
<v>Result = {CallbackMode,State,Data}</v>
- <v>&nbsp;| {CallbackMode,State,Data,Ops}</v>
+ <v>&nbsp;| {CallbackMode,State,Data,Actions}</v>
<v>&nbsp;| {stop,Reason} | ignore</v>
<v>CallbackMode =
<seealso marker="#type-callback_mode">callback_mode()</seealso>
@@ -960,8 +1031,9 @@ erlang:'!' -----> Module:StateName/4
<v>State = <seealso marker="#type-state">state()</seealso></v>
<v>Data = <seealso marker="#type-data">data()</seealso>
</v>
- <v>Ops =
- [<seealso marker="#type-transition_op">transition_op()</seealso>]
+ <v>Actions =
+ [<seealso marker="#type-action">action()</seealso>] |
+ <seealso marker="#type-action">action()</seealso>
</v>
<v>Reason = term()</v>
</type>
@@ -978,7 +1050,7 @@ erlang:'!' -----> Module:StateName/4
function.</p>
<p>If the initialization is successful, the function should
return <c>{CallbackMode,State,Data}</c> or
- <c>{CallbackMode,State,Data,Ops}</c>.
+ <c>{CallbackMode,State,Data,Actions}</c>.
<c>CallbackMode</c> selects the
<seealso marker="#type-callback_mode">callback_mode()</seealso>.
of the <c>gen_statem</c>.
@@ -986,7 +1058,7 @@ erlang:'!' -----> Module:StateName/4
of the <c>gen_statem</c> and
<c>Data</c> the server <seealso marker="#type-data">data()</seealso>
</p>
- <p>The <seealso marker="#type-transition_op"><c>Ops</c></seealso>
+ <p>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_function">state function</seealso>.
@@ -1047,7 +1119,7 @@ erlang:'!' -----> Module:StateName/4
from this or from any other
<seealso marker="#state_function">state function</seealso>
by returning with <c>{reply,Caller,Reply}</c> in
- <seealso marker="#type-transition_op">Ops</seealso>, in
+ <seealso marker="#type-action">Actions</seealso>, in
<seealso marker="#type-reply_action">Replies</seealso>
or by calling
<seealso marker="#reply/2">
@@ -1066,7 +1138,7 @@ erlang:'!' -----> Module:StateName/4
all postponed events will be retried in the new state.
</p>
<p>See
- <seealso marker="#type-transition_op">transition_op()</seealso>
+ <seealso marker="#type-action">action()</seealso>
for options that can be set and actions that can be done
by <c>gen_statem</c> after returning from this function.
</p>
@@ -1102,7 +1174,7 @@ erlang:'!' -----> Module:StateName/4
is terminating.
If it is because another callback function has returned a
stop tuple <c>{stop,Reason}</c> in
- <seealso marker="#type-transition_op">Ops</seealso>,
+ <seealso marker="#type-action">Actions</seealso>,
<c>Reason</c> will have the value specified in that tuple.
If it is due to a failure, <c>Reason</c> is the error reason.
</p>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index fe84a428f6..7fbc1e0f0d 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -45,7 +45,7 @@
[wakeup_from_hibernate/3]).
%% Fix problem for doc build
--export_type([state_callback_result/0]).
+-export_type([transition_option/0,state_callback_result/0]).
%%%==========================================================================
%%% Interface functions.
@@ -53,47 +53,77 @@
-type caller() ::
{To :: pid(), Tag :: term()}. % Reply-to specifier for call
+
-type state() ::
state_name() | % For state callback function StateName/5
term(). % For state callback function handle_event/5
+
-type state_name() :: atom().
+
-type data() :: term().
+
-type event_type() ::
{'call',Caller :: caller()} | 'cast' |
'info' | 'timeout' | 'internal'.
+
-type event_predicate() :: % Return true for the event in question
fun((event_type(), term()) -> boolean()).
+
-type callback_mode() :: 'state_functions' | 'handle_event_function'.
--type transition_op() ::
- %% First NewState and NewData are set,
- %% then all transition_action()s are executed in order of
- %% apperance. Postponing the current event is performed
- %% (iff transition_option() 'postpone' is 'true').
- %% Lastly pending events are processed or if there are
- %% no pending events the server goes into receive
- %% or hibernate (iff transition_option() 'hibernate' is 'true')
- transition_option() | transition_action().
+
-type transition_option() ::
- %% The last of each kind in the transition_op()
- %% list takes precedence
- 'postpone' | % Postpone the current event to a different (=/=) state
- {'postpone', Postpone :: boolean()} |
- 'hibernate' | % Hibernate the server instead of going into receive
- {'hibernate', Hibernate :: boolean()} |
- (Timeout :: timeout()) | % {timeout,Timeout}
- {'timeout', % Generate a ('timeout', Msg, ...) event after Time
- Time :: timeout(), Msg :: term()}.
--type transition_action() ::
- %% These can occur multiple times and are executed in order
- %% of appearence in the transition_op() list
+ postpone() | hibernate() | state_timeout().
+-type postpone() ::
+ %% If 'true' postpone the current event
+ %% and retry it when the state changes (=/=)
+ boolean().
+-type hibernate() ::
+ %% If 'true' hibernate the server instead of going into receive
+ boolean().
+-type state_timeout() ::
+ %% Generate a ('timeout', Msg, ...) event after Time
+ %% unless some other event is delivered
+ Time :: timeout().
+
+-type action() ::
+ %% During a state change:
+ %% * NewState and NewData are set.
+ %% * All action()s are executed in order of apperance.
+ %% * Postponing the current event is performed
+ %% iff 'postpone' is 'true'.
+ %% * A state timer is started iff 'timeout' is set.
+ %% * Pending events are processed or if there are
+ %% no pending events the server goes into receive
+ %% or hibernate (iff 'hibernate' is 'true')
+ %%
+ %% These action()s are executed in order of appearence
+ %% in the containing list. The ones that set options
+ %% will override any previous so the last of each kind wins.
+ %%
+ 'postpone' | % Set the postpone option
+ {'postpone', Postpone :: postpone()} |
+ %%
+ 'hibernate' | % Set the hibernate option
+ {'hibernate', Hibernate :: hibernate()} |
+ %%
+ (Timeout :: state_timeout()) | % {timeout,Timeout}
+ {'timeout', % Set the timeout option
+ Time :: state_timeout(), Msg :: 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()} |
+ %%
{'remove_event', % Remove the oldest matching (=:=) event
EventType :: event_type(), EventContent :: term()} |
{'remove_event', % Remove the oldest event satisfying predicate
EventPredicate :: event_predicate()} |
+ %%
{'cancel_timer', % Cancel timer and clean up mess(ages)
TimerRef :: reference()} |
{'demonitor', % Demonitor and clean up mess(ages)
@@ -103,6 +133,7 @@
-type reply_action() ::
{'reply', % Reply to a caller
Caller :: caller(), Reply :: term()}.
+
-type state_callback_result() ::
{'stop', % Stop the server
Reason :: term()} |
@@ -122,15 +153,16 @@
{'next_state', % State transition, maybe to the same state
NewState :: state(),
NewData :: data(),
- Ops :: [transition_op()] | transition_op()} |
+ Actions :: [action()] | action()} |
{'keep_state', % {keep_state,NewData,[]}
NewData :: data()} |
{'keep_state',
NewData :: data(),
- Ops :: [transition_op()] | transition_op()} |
+ Actions :: [action()] | action()} |
{'keep_state_and_data'} | % {keep_state_and_data,[]}
{'keep_state_and_data',
- Ops :: [transition_op()] | transition_op()}.
+ Actions :: [action()] | action()}.
+
%% The state machine init function. It is called only once and
%% the server is not running until this function has returned
@@ -138,7 +170,7 @@
%% for all events to this server.
-callback init(Args :: term()) ->
{callback_mode(), state(), data()} |
- {callback_mode(), state(), data(), [transition_op()]} |
+ {callback_mode(), state(), data(), [action()] | action()} |
'ignore' |
{'stop', Reason :: term()}.
@@ -434,19 +466,19 @@ enter_loop(Module, Opts, CallbackMode, State, Data) ->
Module :: module(), Opts :: [debug_opt()],
CallbackMode :: callback_mode(),
State :: state(), Data :: data(),
- Server_or_Ops ::
- server_name() | pid() | [transition_op()]) ->
+ Server_or_Actions ::
+ server_name() | pid() | [action()]) ->
no_return().
-enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Ops) ->
+enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Actions) ->
if
- is_list(Server_or_Ops) ->
+ is_list(Server_or_Actions) ->
enter_loop(
Module, Opts, CallbackMode, State, Data,
- self(), Server_or_Ops);
+ self(), Server_or_Actions);
true ->
enter_loop(
Module, Opts, CallbackMode, State, Data,
- Server_or_Ops, [])
+ Server_or_Actions, [])
end.
%%
-spec enter_loop(
@@ -454,11 +486,11 @@ enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Ops) ->
CallbackMode :: callback_mode(),
State :: state(), Data :: data(),
Server :: server_name() | pid(),
- Ops :: [transition_op()]) ->
+ Actions :: [action()] | action()) ->
no_return().
-enter_loop(Module, Opts, CallbackMode, State, Data, Server, Ops) ->
+enter_loop(Module, Opts, CallbackMode, State, Data, Server, Actions) ->
Parent = gen:get_parent(),
- enter(Module, Opts, CallbackMode, State, Data, Server, Ops, Parent).
+ enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent).
%%---------------------------------------------------------------------------
%% API helpers
@@ -480,32 +512,38 @@ do_send(Proc, Msg) ->
end.
%% Here init_it/6 and enter_loop/5,6,7 functions converge
-enter(Module, Opts, CallbackMode, State, Data, Server, Ops, Parent)
+enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent)
when is_atom(Module), is_pid(Parent) ->
case callback_mode(CallbackMode) of
true ->
Name = gen:get_proc_name(Server),
Debug = gen:debug_options(Name, Opts),
PrevState = undefined,
+ NewActions =
+ if
+ is_list(Actions) ->
+ Actions ++ [{postpone,false}];
+ true ->
+ [Actions,{postpone,false}]
+ end,
S = #{
callback_mode => CallbackMode,
module => Module,
name => Name,
prev_state => PrevState,
- state => PrevState, % Discarded by loop_event_transition_ops
+ state => PrevState, % Discarded by loop_event_actions
data => Data,
timer => undefined,
postponed => [],
hibernate => false},
- loop_event_transition_ops(
+ loop_event_actions(
Parent, Debug, S, [],
{event,undefined}, % Discarded due to {postpone,false}
- PrevState, State, Data,
- Ops++[{postpone,false}]);
+ PrevState, State, Data, NewActions);
false ->
erlang:error(
badarg,
- [Module,Opts,CallbackMode,State,Data,Server,Ops,Parent])
+ [Module,Opts,CallbackMode,State,Data,Server,Actions,Parent])
end.
%%%==========================================================================
@@ -536,11 +574,11 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) ->
enter(
Module, Opts, CallbackMode, State, Data,
ServerRef, [], Parent);
- {CallbackMode,State,Data,Ops} ->
+ {CallbackMode,State,Data,Actions} ->
proc_lib:init_ack(Starter, {ok,self()}),
enter(
Module, Opts, CallbackMode, State, Data,
- ServerRef, Ops, Parent);
+ ServerRef, Actions, Parent);
{stop,Reason} ->
gen:unregister_name(ServerRef),
proc_lib:init_ack(Starter, {error,Reason}),
@@ -814,183 +852,209 @@ loop_event_result(
%% Since we got back here Replies was bad
terminate(Class, NewReason, Stacktrace, NewDebug, NewS, Q);
{next_state,NewState,NewData} ->
- loop_event_transition_ops(
+ loop_event_actions(
Parent, Debug, S, Events, Event,
State, NewState, NewData, []);
- {next_state,NewState,NewData,Ops}
- when is_list(Ops) ->
- loop_event_transition_ops(
+ {next_state,NewState,NewData,Actions}
+ when is_list(Actions) ->
+ loop_event_actions(
Parent, Debug, S, Events, Event,
- State, NewState, NewData, Ops);
+ State, NewState, NewData, Actions);
{keep_state,NewData} ->
- loop_event_transition_ops(
+ loop_event_actions(
Parent, Debug, S, Events, Event,
State, State, NewData, []);
- {keep_state,NewData,Ops} ->
- loop_event_transition_ops(
+ {keep_state,NewData,Actions} ->
+ loop_event_actions(
Parent, Debug, S, Events, Event,
- State, State, NewData, Ops);
+ State, State, NewData, Actions);
{keep_state_and_data} ->
- loop_event_transition_ops(
+ loop_event_actions(
Parent, Debug, S, Events, Event,
State, State, Data, []);
- {keep_state_and_data,Ops} ->
- loop_event_transition_ops(
+ {keep_state_and_data,Actions} ->
+ loop_event_actions(
Parent, Debug, S, Events, Event,
- State, State, Data, Ops);
+ State, State, Data, Actions);
_ ->
?TERMINATE(
error, {bad_return_value,Result}, Debug, S, [Event|Events])
end.
-loop_event_transition_ops(
- Parent, Debug0, #{postponed := P0} = S, Events, Event,
- State, NewState, NewData, Ops) ->
- case collect_transition_options(Ops) of
- {Postpone,Hibernate,Timeout,Actions} ->
- P1 = % Move current event to postponed if Postpone
- case Postpone of
- true ->
- [Event|P0];
- false ->
- P0
- end,
- {Q2,P2} = % Move all postponed events to queue if state change
- if
- NewState =:= State ->
- {Events,P1};
- true ->
- {lists:reverse(P1, Events),[]}
- end,
- %%
- case process_transition_actions(
- Actions, Debug0, S, Q2, P2) of
- {Debug,Q3,P} ->
- NewDebug =
- sys_debug(
- Debug, S,
- case Postpone of
- true ->
- {postpone,Event,NewState};
- false ->
- {consume,Event,NewState}
- end),
- {Timer,Q} =
- case Timeout of
- undefined ->
- {undefined,Q3};
- {timeout,0,Msg} ->
- %% Pretend the timeout has just been received
- {undefined,Q3 ++ [{timeout,Msg}]};
- {timeout,Time,Msg} ->
- {erlang:start_timer(Time, self(), Msg),
- Q3}
- end,
- loop_events(
- Parent, NewDebug,
- S#{
- prev_state := State,
- state := NewState,
- data := NewData,
- timer := Timer,
- hibernate := Hibernate,
- postponed := P},
- Q, Timer);
- [Class,Reason,Stacktrace,Debug] ->
- terminate(
- Class, Reason, Stacktrace, Debug, S, [Event|Events])
- end;
- %%
- [Class,Reason,Stacktrace] ->
- terminate(
- Class, Reason, Stacktrace, Debug0, S, [Event|Events])
- end.
-
-%%---------------------------------------------------------------------------
-%% Server helpers
-
-collect_transition_options(Ops) ->
- if
- is_list(Ops) ->
- collect_transition_options(
- Ops, false, false, undefined, []);
- true ->
- collect_transition_options(
- [Ops], false, false, undefined, [])
- end.
-%% Keep the last of each kind
-collect_transition_options(
- [], Postpone, Hibernate, Timeout, Actions) ->
- {Postpone,Hibernate,Timeout,lists:reverse(Actions)};
-collect_transition_options(
- [Op|Ops] = AllOps, Postpone, Hibernate, Timeout, Actions) ->
- case Op of
+loop_event_actions(
+ Parent, Debug, S, Events, Event, State, NewState, NewData, Actions) ->
+ loop_event_actions(
+ Parent, Debug, S, Events, Event, State, NewState, NewData,
+ false, false, undefined, [], Actions).
+%%
+loop_event_actions(
+ Parent, Debug, #{postponed := P0} = S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents, []) ->
+ P1 = % Move current event to postponed if Postpone
+ case Postpone of
+ true ->
+ [Event|P0];
+ false ->
+ P0
+ end,
+ {Timer,Q1} =
+ case Timeout of
+ undefined ->
+ {undefined,Events};
+ {timeout,0,Msg} ->
+ %% Pretend the timeout has just been received
+ {undefined,Events ++ [{timeout,Msg}]};
+ {timeout,Time,Msg} ->
+ {erlang:start_timer(Time, self(), Msg),
+ Events}
+ end,
+ {Q2,P} = % Move all postponed events to queue if state change
+ if
+ NewState =:= State ->
+ {Q1,P1};
+ true ->
+ {lists:reverse(P1, Q1),[]}
+ end,
+ %% Place next events first in queue
+ Q = lists:reverse(NextEvents, Q2),
+ %%
+ NewDebug =
+ sys_debug(
+ Debug, S,
+ case Postpone of
+ true ->
+ {postpone,Event,NewState};
+ false ->
+ {consume,Event,NewState}
+ end),
+ %% Loop to top; process next event
+ loop_events(
+ Parent, NewDebug,
+ S#{
+ prev_state := State,
+ state := NewState,
+ data := NewData,
+ timer := Timer,
+ hibernate := Hibernate,
+ postponed := P},
+ Q, Timer);
+loop_event_actions(
+ Parent, Debug, S, Events, Event, State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents, [Action|Actions]) ->
+ case Action of
+ %% Set options
postpone ->
- collect_transition_options(
- Ops, true, Hibernate, Timeout, Actions);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ true, Hibernate, Timeout, NextEvents, Actions);
{postpone,NewPostpone} when is_boolean(NewPostpone) ->
- collect_transition_options(
- Ops, NewPostpone, Hibernate, Timeout, Actions);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ NewPostpone, Hibernate, Timeout, NextEvents, Actions);
{postpone,_} ->
- [error,{bad_ops,AllOps},?STACKTRACE()];
+ ?TERMINATE(
+ error, {bad_action,Action}, Debug, S, [Event|Events]);
hibernate ->
- collect_transition_options(
- Ops, Postpone, true, Timeout, Actions);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, true, Timeout, NextEvents, Actions);
{hibernate,NewHibernate} when is_boolean(NewHibernate) ->
- collect_transition_options(
- Ops, Postpone, NewHibernate, Timeout, Actions);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, NewHibernate, Timeout, NextEvents, Actions);
{hibernate,_} ->
- [error,{bad_ops,AllOps},?STACKTRACE()];
- {timeout,infinity,_} -> % Ignore since it will never time out
- collect_transition_options(
- Ops, Postpone, Hibernate, undefined, Actions);
+ ?TERMINATE(
+ error, {bad_action,Action}, Debug, S, [Event|Events]);
+ {timeout,infinity,_} -> % Clear timer - it will never trigger
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, undefined, NextEvents, Actions);
{timeout,Time,_} = NewTimeout when is_integer(Time), Time >= 0 ->
- collect_transition_options(
- Ops, Postpone, Hibernate, NewTimeout, Actions);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, NewTimeout, NextEvents, Actions);
{timeout,_,_} ->
- [error,{bad_ops,AllOps},?STACKTRACE()];
- _ -> % Collect others as actions
- collect_transition_options(
- Ops, Postpone, Hibernate, Timeout, [Op|Actions])
- end.
-
-process_transition_actions([], Debug, _S, Q, P) ->
- {Debug,Q,P};
-process_transition_actions(
- [Action|Actions] = AllActions, Debug, S, Q, P) ->
- case Action of
- {reply,{_To,_Tag}=Caller,Reply} ->
- NewDebug = do_reply(Debug, S, Caller, Reply),
- process_transition_actions(Actions, NewDebug, S, Q, P);
+ ?TERMINATE(
+ error, {bad_action,Action}, Debug, S, [Event|Events]);
+ %% Actual actions
+ {reply,Caller,Reply} ->
+ case caller(Caller) of
+ true ->
+ NewDebug = do_reply(Debug, S, Caller, Reply),
+ loop_event_actions(
+ Parent, NewDebug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents, Actions);
+ false ->
+ ?TERMINATE(
+ error, {bad_action,Action}, Debug, S, [Event|Events])
+ end;
{next_event,Type,Content} ->
case event_type(Type) of
true ->
- process_transition_actions(
- Actions, Debug, S, [{Type,Content}|Q], P);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout,
+ [{Type,Content}|NextEvents], Actions);
false ->
- [error,{bad_ops,AllActions},?STACKTRACE(),Debug]
+ ?TERMINATE(
+ error, {bad_action,Action}, Debug, S, [Event|Events])
end;
_ ->
%% All others are remove actions
case remove_fun(Action) of
false ->
- process_transition_actions(
- Actions, Debug, S, Q, P);
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents, Actions);
undefined ->
- [error,{bad_ops,AllActions},?STACKTRACE(),Debug];
+ ?TERMINATE(
+ error, {bad_action,Action}, Debug, S, [Event|Events]);
RemoveFun when is_function(RemoveFun, 2) ->
- case remove_event(RemoveFun, Q, P) of
- {NewQ,NewP} ->
- process_transition_actions(
- Actions, Debug, S, NewQ, NewP);
- Error ->
- Error ++ [Debug]
+ #{postponed := P} = S,
+ case remove_event(RemoveFun, Events, P) of
+ false ->
+ loop_event_actions(
+ Parent, Debug, S, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents,
+ Actions);
+ {NewEvents,false} ->
+ loop_event_actions(
+ Parent, Debug, S, NewEvents, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents,
+ Actions);
+ {false,NewP} ->
+ NewS = S#{postponed := NewP},
+ loop_event_actions(
+ Parent, Debug, NewS, Events, Event,
+ State, NewState, NewData,
+ Postpone, Hibernate, Timeout, NextEvents,
+ Actions);
+ [Class,Reason,Stacktrace] ->
+ terminate(
+ Class, Reason, Stacktrace,
+ Debug, S, [Event|Events])
end;
- Error ->
- Error ++ [Debug]
+ [Class,Reason,Stacktrace] ->
+ terminate(
+ Class, Reason, Stacktrace, Debug, S, [Event|Events])
end
end.
+%%---------------------------------------------------------------------------
+%% Server helpers
+
reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) ->
if
is_list(Replies) ->
@@ -1004,14 +1068,14 @@ reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) ->
do_reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, []) ->
terminate(Class, Reason, Stacktrace, Debug, S, Q);
do_reply_then_terminate(
- Class, Reason, Stacktrace, Debug, S, Q, [R|Rs] = Replies) ->
+ Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) ->
case R of
{reply,{_To,_Tag}=Caller,Reply} ->
NewDebug = do_reply(Debug, S, Caller, Reply),
do_reply_then_terminate(
Class, Reason, Stacktrace, NewDebug, S, Q, Rs);
_ ->
- [error,{bad_replies,Replies},?STACKTRACE(),Debug]
+ [error,{bad_action,R},?STACKTRACE(),Debug]
end.
do_reply(Debug, S, Caller, Reply) ->
@@ -1026,20 +1090,19 @@ remove_event(RemoveFun, Q, P) ->
false ->
case remove_head_event(RemoveFun, Q) of
false ->
- {P,Q};
+ false;
NewQ ->
- {P,NewQ}
+ {false,NewQ}
end;
NewP ->
- {NewP,Q}
+ {NewP,false}
end
catch
Class:Reason ->
[Class,Reason,erlang:get_stacktrace()]
end.
-%% Do the given transition action and create
-%% an event removal predicate fun()
+%% Do the given action and create an event removal predicate fun()
remove_fun({remove_event,Type,Content}) ->
fun (T, C) when T =:= Type, C =:= Content -> true;
(_, _) -> false