diff options
author | Raimo Niskanen <[email protected]> | 2018-06-21 09:53:55 +0200 |
---|---|---|
committer | Raimo Niskanen <[email protected]> | 2018-09-10 12:28:23 +0200 |
commit | 61935c77915a8e206a82cba1c9c9f64be462905d (patch) | |
tree | 02629ef923a9f4ab18b7aeb97baa3efa3bb68be2 | |
parent | d46bcb823a3c2b67dd4f965ea70aff869295932b (diff) | |
download | otp-61935c77915a8e206a82cba1c9c9f64be462905d.tar.gz otp-61935c77915a8e206a82cba1c9c9f64be462905d.tar.bz2 otp-61935c77915a8e206a82cba1c9c9f64be462905d.zip |
Clean up and optimize code and doc
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 228 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 261 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 73 | ||||
-rw-r--r-- | system/doc/design_principles/statem.xml | 277 |
4 files changed, 474 insertions, 365 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,_} = diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 98fd1fd69d..29e19163a5 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -44,28 +44,40 @@ <title>Event-Driven State Machines</title> <p> Established Automata Theory does not deal much with - how a state transition is triggered, + how a <em>state transition</em> is triggered, but assumes that the output is a function of the input (and the state) and that they are some kind of values. </p> <p> For an Event-Driven State Machine, the input is an event - that triggers a state transition and the output - is actions executed during the state transition. + that triggers a <em>state transition</em> and the output + is actions executed during the <em>state transition</em>. It can analogously to the mathematical model of a - Finite-State Machine be described as + Finite State Machine be described as a set of relations of the following form: </p> <pre> State(S) x Event(E) -> Actions(A), State(S')</pre> - <p>These relations are interpreted as follows: + <p> + These relations are interpreted as follows: if we are in state <c>S</c> and event <c>E</c> occurs, we are to perform actions <c>A</c> and make a transition to state <c>S'</c>. Notice that <c>S'</c> can be equal to <c>S</c> and that <c>A</c> can be empty. </p> <p> + In <c>gen_statem</c> we define + a <em>state change</em> as a <em>state transition</em> + in which the new state <c>S'</c> is different from + the current state <c>S</c>, where "different" means + Erlang's strict inequality: <c>=/=</c> + also know as "does not match". + During a <em>state changes</em>, + <c>gen_statem</c> does more things + than during other <em>state transitions</em>. + </p> + <p> As <c>A</c> and <c>S'</c> depend only on <c>S</c> and <c>E</c>, the kind of state machine described here is a Mealy machine @@ -95,8 +107,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <list type="bulleted"> <item> Co-located callback code for each state, - regardless of - <seealso marker="#Event Types">Event Type</seealso> + for all + <seealso marker="#Event Types"><em>Event Types</em></seealso> (such as <em>call</em>, <em>cast</em> and <em>info</em>) </item> <item> @@ -114,13 +126,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </item> <item> <seealso marker="#State Enter Calls"> - State Enter Calls + <em>State Enter Calls</em> </seealso> (callback on state entry co-located with the rest of each state's callback code) </item> <item> - Easy-to-use timeouts + Easy-to-use time-outs (<seealso marker="#State Time-Outs">State Time-Outs</seealso>, <seealso marker="#Event Time-Outs">Event Time-Outs</seealso> and @@ -152,11 +164,11 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <marker id="Callback Module" /> <title>Callback Module</title> <p> - The callback module contains functions that implement + The <em>callback module</em> contains functions that implement the state machine. When an event occurs, the <c>gen_statem</c> behaviour engine - calls a function in the callback module with the event, + calls a function in the <em>callback module</em> with the event, current state and server data. This function performs the actions for this event, and returns the new state and server data @@ -166,7 +178,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> The behaviour engine holds the state machine state, server data, timer references, a queue of posponed messages and other metadata. It receives all process messages, - handles the system messages, and calls the callback module + handles the system messages, and calls the <em>callback module</em> with machine specific events. </p> </section> @@ -177,7 +189,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <marker id="Callback Modes" /> <title>Callback Modes</title> <p> - The <c>gen_statem</c> behavior supports two callback modes: + The <c>gen_statem</c> behavior supports two <em>callback modes</em>: </p> <taglist> <tag> @@ -202,31 +214,33 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </item> </taglist> <p> - The callback mode is selected at server start + The <em>callback mode</em> is selected at server start and may be changed with a code upgrade/downgrade. </p> <p> See the section - <seealso marker="#Event Handler">Event Handler</seealso> + <seealso marker="#State Callback"><em>State Callback</em></seealso> that describes the event handling callback function(s). </p> <p> - The callback mode is selected by implementing + The <em>callback mode</em> is selected by implementing a mandatory callback function <seealso marker="stdlib:gen_statem#Module:callback_mode/0"> <c>Module:callback_mode()</c> </seealso> - that returns one of the callback modes. + that returns one of the <em>callback modes</em>. </p> <p> The <seealso marker="stdlib:gen_statem#Module:callback_mode/0"> <c>Module:callback_mode()</c> </seealso> - function may also return a list containing the callback mode + function may also return a list containing the <em>callback mode</em> and the atom <c>state_enter</c> in which case - <seealso marker="#State Enter Calls">State Enter Calls</seealso> - are activated for the callback mode. + <seealso marker="#State Enter Calls"> + <em>state enter calls</em> + </seealso> + are activated for the <em>callback mode</em>. </p> <section> @@ -237,11 +251,11 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> it is the one most like <c>gen_fsm</c>. But if you do not want the restriction that the state must be an atom, or if you do not want to write - one event handler function per state; please read on... + one <em>state callback</em> function per state; please read on... </p> <p> The two - <seealso marker="#Callback Modes">Callback Modes</seealso> + <seealso marker="#Callback Modes"><em>callback modes</em></seealso> give different possibilities and restrictions, with one common goal: to handle all possible combinations of events and states. @@ -257,7 +271,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> With <c>state_functions</c>, you are restricted to use atom-only states, and the <c>gen_statem</c> engine branches depending on state name for you. - This encourages the callback module to co-locate + This encourages the <em>callback module</em> to co-locate the implementation of all event actions particular to one state in the same place in the code, hence to focus on one state at the time. @@ -302,11 +316,12 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <!-- =================================================================== --> <section> - <marker id="Event Handler" /> - <title>Event Handler</title> + <marker id="State Callback" /> + <title>State Callback</title> <p> - Which callback function that handles an event - depends on the callback mode: + The <em>state callback</em> is the callback function + that handles an event in the current state, + and which function that is depends on the <em>callback mode</em>: </p> <taglist> <tag><c>state_functions</c></tag> @@ -329,7 +344,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> <p> See section - <seealso marker="#One Event Handler">One Event Handler</seealso> + <seealso marker="#One State Callback"> + <em>One State Callback</em> + </seealso> for an example. </p> </item> @@ -338,15 +355,17 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> The state is either the name of the function itself or an argument to it. The other arguments are the <c>EventType</c> described in section <seealso marker="#Event Types">Event Types</seealso>, - the event dependent <c>EventContent</c>, and the current server <c>Data</c>. + the event dependent <c>EventContent</c>, + and the current server <c>Data</c>. </p> <p> - State enter calls are also handled by the event handler and have - slightly different arguments. See the section + <em>State enter calls</em> are also handled by the event handler + and have slightly different arguments. See section <seealso marker="#State Enter Calls">State Enter Calls</seealso>. </p> <p> - The event handler return values are defined in the description of + The <em>state callback</em> return values + are defined in the description of <seealso marker="stdlib:gen_statem#Module:StateName/3"> <c>Module:StateName/3</c> </seealso> @@ -361,24 +380,29 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <item> <p> Set next state and update the server data. - If the <c>Actions</c> field is used, execute state transition actions. - An empty <c>Actions</c> list is equivalent to not returning the field. + If the <c>Actions</c> field is used, + execute <em>transition actions</em>. + An empty <c>Actions</c> list is equivalent to + not returning the field. </p> <p> See section - <seealso marker="#State Transition Actions"> - State Transition Actions + <seealso marker="#Transition Actions"> + <em>Transition Actions</em> </seealso> for a list of possible - state transition actions. + <em>transition actions</em>. </p> <p> - If <c>NextState =/= State</c> the state machine changes - to a new state. A + If <c>NextState =/= State</c> this is a <em>state change</em> + so the extra things <c>gen_statem</c> does are: the event queue + is restarted from the oldest + <seealso marker="#Postponing Events">postponed event</seealso>, + any current + <seealso marker="#State Time-Outs">state time-out</seealso> + is cancelled, and a <seealso marker="#State Enter Calls">state enter call</seealso> - is performed if enabled and all - <seealso marker="#Postponing Events">postponed events</seealso> - are retried. + is performed, if enabled. </p> </item> <tag> @@ -388,7 +412,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <item> <p> Same as the <c>next_state</c> values with - <c>NextState =:= State</c>, that is, no state change. + <c>NextState =:= State</c>, that is, no <em>state change</em>. </p> </item> <tag> @@ -414,9 +438,16 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <seealso marker="#State Enter Calls"> State Enter Calls </seealso> - are enabled, repeat the state enter call + are enabled, repeat the <em>state enter call</em> as if this state was entered again. </p> + <p> + If these return values are used from a + <em>state enter call</em> the <c>OldState</c> does not change, + but if used from an event handling <em>state callback</em> + the new <em>state enter call's</em> <c>OldState</c> + will be the current state. + </p> </item> <tag> <c>{stop, Reason, NewData}</c><br /> @@ -435,7 +466,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <item> <p> Same as the <c>stop</c> values, but first execute the given - state transition actions that may only be reply actions. + <seealso marker="#Transition Actions"> + <em>transition actions</em> + </seealso> + that may only be reply actions. </p> </item> </taglist> @@ -449,8 +483,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <c>Module:init(Args)</c> </seealso> callback function is called before any - <seealso marker="#Event Handler">Event Handler</seealso> - is called. This function behaves like an event handler + <seealso marker="#State Callback"><em>state callback</em></seealso> + is called. This function behaves like an <em>state callback</em> function, but gets its only argument <c>Args</c> from the <c>gen_statem</c> <seealso marker="stdlib:gen_statem#start/3"> @@ -474,8 +508,8 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <!-- =================================================================== --> <section> - <marker id="State Transition Actions" /> - <title>State Transition Actions</title> + <marker id="Transition Actions" /> + <title>Transition Actions</title> <p> In the first section <seealso marker="#Event-Driven State Machines"> @@ -483,13 +517,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> actions were mentioned as a part of the general state machine model. These general actions - are implemented with the code that callback module + are implemented with the code that <em>callback module</em> <c>gen_statem</c> executes in an event-handling callback function before returning to the <c>gen_statem</c> engine. </p> <p> - There are more specific state-transition actions + There are more specific <em>transition actions</em> that a callback function can command the <c>gen_statem</c> engine to do after the callback function return. These are commanded by returning a list of @@ -500,7 +534,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> from the <seealso marker="stdlib:gen_statem#Module:StateName/3">callback function</seealso>. - These are the possible state transition actions: + These are the possible <em>transition actions</em>: </p> <taglist> <tag> @@ -596,13 +630,13 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <title>Event Types</title> <p> Events are categorized in different - <seealso marker="stdlib:gen_statem#type-event_type">event types</seealso>. + <seealso marker="stdlib:gen_statem#type-event_type"><em>event types</em></seealso>. Events of all types are for a given state handled in the same callback function, and that function gets <c>EventType</c> and <c>EventContent</c> as arguments. </p> <p> - The following is a complete list of event types and where + The following is a complete list of <em>event types</em> and where they come from: </p> <taglist> @@ -624,7 +658,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> Generated by <seealso marker="stdlib:gen_statem#call/2"><c>gen_statem:call</c></seealso>, where <c>From</c> is the reply address to use - when replying either through the state transition action + when replying either through the <em>transition action</em> <c>{reply,From,Msg}</c> or by calling <seealso marker="stdlib:gen_statem#reply/1"><c>gen_statem:reply</c></seealso>. </item> @@ -643,7 +677,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> </tag> <item> - Generated by state transition action + Generated by <em>transition action</em> <seealso marker="stdlib:gen_statem#type-state_timeout"> <c>{state_timeout,Time,EventContent}</c> </seealso> @@ -655,7 +689,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> </tag> <item> - Generated by state transition action + Generated by <em>transition action</em> <seealso marker="stdlib:gen_statem#type-generic_timeout"> <c>{{timeout,Name},Time,EventContent}</c> </seealso> @@ -667,7 +701,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> </tag> <item> - Generated by state transition action + Generated by <em>transition action</em> <seealso marker="stdlib:gen_statem#type-event_timeout"> <c>{timeout,Time,EventContent}</c> </seealso> @@ -680,10 +714,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> </seealso> </tag> <item> - Generated by state transition + Generated by <em>state transition</em> <seealso marker="stdlib:gen_statem#type-action">action</seealso> <c>{next_event,internal,EventContent}</c>. - All event types above can also be generated using + All <em>event types</em> above can also be generated using <c>{next_event,EventType,EventContent}</c>. </item> </taglist> @@ -696,14 +730,14 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <title>State Enter Calls</title> <p> The <c>gen_statem</c> behavior can if this is enabled, - regardless of callback mode, + regardless of <em>callback mode</em>, automatically <seealso marker="stdlib:gen_statem#type-state_enter"> call the state callback </seealso> with special arguments whenever the state changes so you can write state enter actions - near the rest of the state transition rules. + near the rest of the <em>state transition</em> rules. It typically looks like this: </p> <pre> @@ -714,33 +748,35 @@ StateName(EventType, EventContent, Data) -> ... code for actions here ... {next_state, NewStateName, NewData}.</pre> <p> - Since the state enter call is not an event there are restrictions + Since the <em>state enter call</em> is not an event there are restrictions on the allowed return value and <seealso marker="#State Transition Actions">State Transition Actions</seealso>. You may not change the state, <seealso marker="#Postponing Events">postpone</seealso> this non-event, or - <seealso marker="#Inserted Events">insert events</seealso>. + <seealso marker="#Inserted Events">insert any events</seealso>. </p> <p> - The first state that is entered will get a state enter call + The first state that is entered + will get a <em>state enter call</em> with <c>OldState</c> equal to the current state. </p> <p> - You may repeat the state enter call using the <c>{repeat_state,...}</c> + You may repeat the <em>state enter call</em> + using the <c>{repeat_state,...}</c> return value from the - <seealso marker="#Event Handler">Event Handler</seealso>. + <seealso marker="#State Callback">state callback</seealso>. In this case <c>OldState</c> will also be equal to the current state. </p> <p> Depending on how your state machine is specified, - this can be a very useful feature, - but it forces you to handle the state enter calls in all states. + this can be a very useful feature, but it forces you to handle + the <em>state enter calls</em> in all states. See also the <seealso marker="#State Enter Actions"> State Enter Actions </seealso> - chapter. + section. </p> </section> @@ -765,7 +801,7 @@ StateName(EventType, EventContent, Data) -> </image> <p> This code lock state machine can be implemented using - <c>gen_statem</c> with the following callback module: + <c>gen_statem</c> with the following <em>callback module</em>: </p> <code type="erl"><![CDATA[ -module(code_lock). @@ -868,7 +904,8 @@ start_link(Code) -> <item> <p> The second argument, <c>?MODULE</c>, is the name of - the callback module, that is, the module where the callback + the <em>callback module</em>, that is, + the module where the callback functions are located, which is this module. </p> <p> @@ -935,7 +972,7 @@ init(Code) -> <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso> selects the <seealso marker="#Callback Modes"><c>CallbackMode</c></seealso> - for the callback module, in this case + for the <em>callback module</em>, in this case <seealso marker="stdlib:gen_statem#type-callback_mode"><c>state_functions</c></seealso>. That is, each state has got its own handler function: </p> @@ -1051,11 +1088,11 @@ open(state_timeout, lock, Data) -> ]]></code> <p> The timer for a state time-out is automatically cancelled - when the state machine changes states. You can restart - a state time-out by setting it to a new time, which cancels - the running timer and starts a new. This implies that - you can cancel a state time-out by restarting it with - time <c>infinity</c>. + when the state machine does a <em>state change</em>. + You can restart a state time-out by setting it to a new time, + which cancels the running timer and starts a new. + This implies that you can cancel a state time-out + by restarting it with time <c>infinity</c>. </p> </section> @@ -1137,7 +1174,7 @@ open(...) -> ... ; care about what it is. </p> <p> - If the common event handler needs to know the current state + If the common <em>state callback</em> needs to know the current state a function <c>handle_common/4</c> can be used instead: </p> <code type="erl"><![CDATA[ @@ -1149,12 +1186,12 @@ open(...) -> ... ; <!-- =================================================================== --> <section> - <marker id="One Event Handler" /> - <title>One Event Handler</title> + <marker id="One State Callback" /> + <title>One State Callback</title> <p> If <seealso marker="#Callback Modes"> - Callback Mode + <em>callback mode</em> </seealso> <c>handle_event_function</c> is used, all events are handled in @@ -1289,7 +1326,10 @@ stop() -> You get either an event or a time-out, but not both. </p> <p> - It is ordered by the state transition action + It is ordered by the + <seealso marker="#Transition Actions"> + <em>transition action</em> + </seealso> <c>{timeout,Time,EventContent}</c>, or just an integer <c>Time</c>, even without the enclosing actions list (the latter is a form inherited from <c>gen_fsm</c>. @@ -1315,7 +1355,7 @@ locked( ]]></code> <p> Whenever we receive a button event we start an event time-out - of 30 seconds, and if we get an event type <c>timeout</c> + of 30 seconds, and if we get an <em>event type</em> of <c>timeout</c> we reset the remaining code sequence. </p> <p> @@ -1327,7 +1367,7 @@ locked( </p> <p> Note that an event time-out does not work well with - when you have for example a status call as in + when you have for example a status call as in section <seealso marker="#All State Events">All State Events</seealso>, or handle unknown events, since all kinds of events will cancel the event time-out. @@ -1383,14 +1423,14 @@ open(cast, {button,_}, Data) -> ]]></code> <p> Specific generic time-outs can just as - <seealso marker="#State Time-Outs">State Time-Outs</seealso> + <seealso marker="#State Time-Outs">state time-outs</seealso> be restarted or cancelled by setting it to a new time or <c>infinity</c>. </p> <p> - In this particular case we do not need to cancel the timeout - since the timeout event is the only possible reason to - change the state from <c>open</c> to <c>locked</c>. + In this particular case we do not need to cancel the time-out + since the time-out event is the only possible reason to + do a <em>state change</em> from <c>open</c> to <c>locked</c>. </p> <p> Instead of bothering with when to cancel a time-out, @@ -1442,7 +1482,7 @@ open(cast, {button,_}, Data) -> ]]></code> <p> Removing the <c>timer</c> key from the map when we - change to state <c>locked</c> is not strictly + do a <em>state change</em> to <c>locked</c> is not strictly necessary since we can only get into state <c>open</c> with an updated <c>timer</c> map value. But it can be nice to not have outdated values in the state <c>Data</c>! @@ -1474,13 +1514,13 @@ open(cast, {button,_}, Data) -> <p> If you want to ignore a particular event in the current state and handle it in a future state, you can postpone the event. - A postponed event is retried after the state has - changed, that is, <c>OldState =/= NewState</c>. + A postponed event is retried after a <em>state change</em>, + that is, <c>OldState =/= NewState</c>. </p> <p> - Postponing is ordered by the state transition - <seealso marker="#State Transition Actions"> - State Transition Action + Postponing is ordered by the + <seealso marker="#Transition Actions"> + <em>transition action</em> </seealso> <c>postpone</c>. </p> @@ -1496,7 +1536,8 @@ open(cast, {button,_}, Data) -> ... ]]></code> <p> - Since a postponed event is only retried after a state change, + Since a postponed event is only retried + after a <em>state change</em>, you have to think about where to keep a state data item. You can keep it in the server <c>Data</c> or in the <c>State</c> itself, @@ -1505,7 +1546,7 @@ open(cast, {button,_}, Data) -> (see section <seealso marker="#Complex State">Complex State</seealso>) with - <seealso marker="#Callback Modes">Callback Mode</seealso> + <seealso marker="#Callback Modes"><em>callback mode</em></seealso> <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso>. If a change in the value changes the set of events that is handled, then the value should be kept in the State. @@ -1606,17 +1647,17 @@ do_unlock() -> <seealso marker="stdlib:sys"><c>sys</c></seealso> compatible behaviors must respond to system messages and therefore do that in their engine receive loop, - passing non-system messages to the callback module. + passing non-system messages to the <em>callback module</em>. </p> <p> The - <seealso marker="#State Transition Actions"> - State Transition Action + <seealso marker="#Transition Actions"> + <em>transition action</em> </seealso> <c>postpone</c> is designed to model selective receives. A selective receive implicitly postpones any not received events, but the <c>postpone</c> - state transition action explicitly postpones one received event. + <em>transition action</em> explicitly postpones one received event. </p> <p> Both mechanisms have the same theoretical @@ -1638,14 +1679,17 @@ do_unlock() -> (described in the next section), especially if just one or a few states has got state enter actions, this is a perfect use case for the built in - <seealso marker="#State Enter Calls">State Enter Calls</seealso>. + <seealso marker="#State Enter Calls"><em>state enter calls</em></seealso>. </p> <p> You return a list containing <c>state_enter</c> from your - <seealso marker="stdlib:gen_statem#Module:callback_mode/0"><c>callback_mode/0</c></seealso> + <seealso marker="stdlib:gen_statem#Module:callback_mode/0"> + <c>callback_mode/0</c> + </seealso> function and the <c>gen_statem</c> engine will call your - state callback once with the arguments - <c>(enter, OldState, ...)</c> whenever the state changes. + <em>state callback</em> once with an event + <c>(enter, OldState, ...)</c> + whenever it does a <em>state change</em>. Then you just need to handle these event-like calls in all states. </p> <code type="erl"><![CDATA[ @@ -1700,8 +1744,8 @@ open(state_timeout, lock, Data) -> It can sometimes be beneficial to be able to generate events to your own state machine. This can be done with the - <seealso marker="#State Transition Actions"> - State Transition Action + <seealso marker="#Transition Actions"> + <em>transition action</em> </seealso> <c>{next_event,EventType,EventContent}</c>. </p> @@ -1731,11 +1775,9 @@ open(state_timeout, lock, Data) -> </p> <p> A variant of this is to use a - <seealso marker="#Complex State"> - Complex State - </seealso> + <seealso marker="#Complex State">complex state</seealso> with - <seealso marker="#One Event Handler">One Event Handler</seealso>. + <seealso marker="#One State Callback"><em>one state callback</em></seealso>. The state is then modeled with for example a tuple <c>{MainFSMState,SubFSMState}</c>. </p> @@ -1795,7 +1837,7 @@ open(internal, {button,_}, Data) -> <title>Example Revisited</title> <p> This section includes the example after most of the mentioned - modifications and some more using state enter calls, + modifications and some more using <em>state enter calls</em>, which deserves a new state diagram: </p> <!-- The image is edited with dia in a .dia file, @@ -1920,7 +1962,8 @@ terminate(_Reason, State, _Data) -> This section describes what to change in the example to use one <c>handle_event/4</c> function. The previously used approach to first branch depending on event - does not work that well here because of the state enter calls, + does not work that well here + because of the <em>state enter calls</em>, so this example first branches depending on state: </p> <code type="erl"><![CDATA[ @@ -2059,7 +2102,7 @@ format_status(Opt, [_PDict,State,Data]) -> <marker id="Complex State" /> <title>Complex State</title> <p> - The callback mode + The <em>callback mode</em> <seealso marker="stdlib:gen_statem#type-callback_mode"><c>handle_event_function</c></seealso> enables using a non-atom state as described in section <seealso marker="#Callback Modes">Callback Modes</seealso>, @@ -2068,7 +2111,7 @@ format_status(Opt, [_PDict,State,Data]) -> <p> One reason to use this is when you have a state item that when changed should cancel the - <seealso marker="#State Time-Outs">State Time-Out</seealso>, + <seealso marker="#State Time-Outs">state time-out</seealso>, or one that affects the event handling in combination with postponing events. We will go for the latter and complicate the previous example @@ -2104,7 +2147,7 @@ x so it is not to be recognized as the lock button. Or we can make the lock button part of the state so when we then change the lock button in the locked state, - the change becomes a state change + the change becomes a <em>state change</em> and all postponed events are retried, therefore the lock is immediately locked! </p> @@ -2258,7 +2301,7 @@ handle_event(enter, _OldState, {open,_}, _Data) -> </p> <p> Another not uncommon scenario is to use the - <seealso marker="#Event Time-Outs">Event Time-Out</seealso> + <seealso marker="#Event Time-Outs">event time-out</seealso> to trigger hibernation after a certain time of inactivity. There is also a server start option <seealso marker="stdlib:gen_statem#type-hibernate_after_opt"> |