diff options
Diffstat (limited to 'lib/stdlib')
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 130 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 15 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 2 |
3 files changed, 91 insertions, 56 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index f608e10469..5de1812b41 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -110,11 +110,28 @@ erlang:'!' -----> Module:StateName/5 states so you do not accidentally postpone one event forever creating an infinite busy loop. </p> - <p> - An event can can be postponed causing it to be retried - after the state has changed, or more precisely; - after a state change all postponed events are retried - in the new state. + <p>The gen_statem enqueues incoming events in order of arrival + and presents these to the + <seealso marker="#state_function">state function</seealso> + in that order. The state function can postpone an event + so it is not retried in the current state. + After a state change all enqueued events (including postponed) + are again presented to the state function. + </p> + <p>The + <seealso marker="#state_function">state function</seealso> + can insert events using the + <seealso marker="#type-state_operation"> + <c>state_operation()</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 + the oldest incoming event. There is a dedicated + <seealso marker="#type-event_type"> + <c>event_type()</c> <c>internal</c> + </seealso> + that can be used for such events making it impossible + to mistake for an external event. </p> <p>A gen_statem handles system messages as documented in <seealso marker="sys">sys</seealso>. @@ -302,7 +319,12 @@ erlang:'!' -----> Module:StateName/5 <datatype> <name name="init_option" /> <desc> - <p>Option that only is valid when initializing the gen_statem</p> + <p>Option that only is valid when initializing the gen_statem + that is it can be returned from + <seealso marker="#Module:init/1">Module:init/1</seealso> + or given to + <seealso marker="#enter_loop/5">enter_loop/5,6</seealso>. + </p> </desc> </datatype> <datatype> @@ -332,7 +354,7 @@ erlang:'!' -----> Module:StateName/5 <p>Either a <seealso marker="#type-state_option"> <c>state_option()</c> - </seealso> of which the first occurence + </seealso> of which the last occurence in the containing list takes precedence, or a <seealso marker="#type-state_operation"> <c>state_operation()</c> @@ -340,42 +362,34 @@ erlang:'!' -----> Module:StateName/5 the containing list. </p> <p>These may be returned from the - <seealso marker="#state_function">state function</seealso> - or from <seealso marker="#Module:init/1">Module:init/1</seealso>. - </p> - <p>The gen_statem enqueues postponed events and - not yet processed events in order of arrival, except for - an event that a callback function inserts with - <c>{insert_event,EventType,EventContent}</c> that is inserted - as the next event to process. + <seealso marker="#state_function">state function</seealso>, + from <seealso marker="#Module:init/1">Module:init/1</seealso> + or given to + <seealso marker="#enter_loop/5">enter_loop/5,6</seealso>. </p> - <p>When the state machine changes states all enqueued events - becomes not yet processed to be processed before the old - not yet processed events. In other words; the order of arrival - is retained. - </p> - <p>The processing order is:</p> + <p>The processing order for a state change is:</p> <list type="ordered"> <item>If the option <c>retry</c> is <c>true</c> - the current event is enqueued as postponed to be retried. + the current event is postponed. </item> - <item>If the state changes all postponed events - are transferred to not yet processed to be processed - before other not yet processed events. + <item>If the state changes the queue of incoming events + is reset to start with the oldest postponed. </item> <item>All operations are processed in order of appearance. </item> - <item>The <c>timeout</c> option is processed if present. - So a state timer may be started or a timeout zero event - may be inserted as if just received. + <item>The <c>timeout</c> option is processed if present, + so 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 next not yet processed event - if there is any, otherwise the gen_statem goes into <c>receive</c> + is called with the oldest enqueued event if there is any, + otherwise the gen_statem 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 - non-system event awakens the gen_statem. + non-system event awakens the gen_statem, or rather + the next incoming message awakens the gen_statem but if it + is a system event it goes back into hibernation. </item> </list> </desc> @@ -383,12 +397,21 @@ erlang:'!' -----> Module:StateName/5 <datatype> <name name="state_option" /> <desc> + <p>If multiple state options of the same type are present + in the containing list these are set in the list order + and the last value is kept. + </p> <taglist> <tag><c>retry</c></tag> <tag><c>{retry,<anno>Retry</anno>}</c></tag> <item>If <c><anno>Retry</anno> =:= true</c> or plain <c>retry</c> postpone the current event to be retried after a state change. + This option 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> <tag><c>{hibernate,<anno>Hibernate</anno>}</c></tag> @@ -397,9 +420,9 @@ erlang:'!' -----> Module:StateName/5 <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 not yet processed events the - <c>hibernate</c> operation is ignored as if an event - just arrived and awakened the gen_statem. + If there are enqueued events the <c>hibernate</c> operation + is ignored as if an event just arrived and awakened + the gen_statem. </item> <tag> <c>{timeout,<anno>Time</anno>,<anno>Msg</anno>}</c> @@ -426,14 +449,20 @@ erlang:'!' -----> Module:StateName/5 <datatype> <name name="state_operation" /> <desc> + <p>The state operations are executed in the containing + list order. This matters for <c>next_event</c> where + the last one in the list will become the next event to present + to the state functions. Regarding the other operations + it is only for <c>{remove_event,<anno>EventPredicate</anno>}</c> + that the order may matter. + </p> <taglist> <tag> <c> - {insert_event,<anno>EventType</anno>,<anno>EventContent</anno>} + {next_event,<anno>EventType</anno>,<anno>EventContent</anno>} </c> </tag> - <item>Insert the given event as the next to process - before any other not yet processed events. + <item>Insert the given event as the next to process. An event of type <seealso marker="#type-event_type"> <c>internal</c> @@ -982,7 +1011,13 @@ erlang:'!' -----> Module:StateName/5 <seealso marker="#call/2">gen_statem:call/2</seealso>, <seealso marker="#cast/2">gen_statem:cast/2</seealso> or as a normal process message this function is called. - If the <c>EventType</c> is + If + <seealso marker="#type-callback_mode">callback_mode</seealso> + is <c>state_functions</c> then <c>Module:StateName/5</c> is called, + and if it is <c>handle_event_function</c> + then <c>Module:handle_event/5</c> is called. + </p> + <p>If <c>EventType</c> is <seealso marker="#type-event_type"><c>{call,Client}</c></seealso> the client is waiting for a reply. The reply can be sent from this or from any other @@ -995,23 +1030,24 @@ erlang:'!' -----> Module:StateName/5 <c>gen_statem:reply(Client, Reply)</c> </seealso>. </p> - <p><c>StateName</c> is rarely useful. One exception is if - you call a common event handling function from your state + <p><c>StateName</c> is useful in some odd cases for example + if you call a common event handling function from your state function then you might want to pass <c>StateName</c>. </p> - <p><c>PrevStateName</c> and <c>PrevState</c> are rarely useful. - They can be used when you want to do something only at the - first event in a state. Note that when gen_statem enters - its first state this is set to a <c>reference()</c> since - that can not match the state. + <p><c>PrevStateName</c> and <c>PrevState</c> are useful + in some odd cases for example when you want to do something + only at the first event in a state. + Note that when gen_statem enters its first state + this is set to a <c>reference()</c> + since that can not match equal to any state. </p> <p>If this function returns with a new state that does not match equal (<c>=/=</c>) to the current state all postponed events will be retried in the new state. </p> <p>See <seealso marker="#type-state_op">state_op()</seealso> - for the operations that can be done by gen_statem - after returning from this function. + for options that can be set and operations that can be done + by gen_statem after returning from this function. </p> </desc> </func> diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 782e027b29..52fde7df7b 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -88,7 +88,7 @@ %% These can occur multiple times and are executed in order %% of appearence in the state_op() list reply_operation() | - {'insert_event', % Insert event as the next to handle + {'next_event', % Insert event as the next to handle EventType :: event_type(), EventContent :: term()} | {'remove_event', % Remove the oldest matching (=:=) event @@ -469,7 +469,7 @@ enter(Module, Options, State, StateData, Server, InitOps, Parent) -> S#{callback_mode := CallbackMode}, [], {event,undefined}, State, StateData, - [{retry,false}|StateOps]); + StateOps++[{retry,false}]); [Reason] -> ?TERMINATE(Reason, Debug, S, []) end. @@ -846,10 +846,10 @@ loop_event_state_ops( %% Server helpers collect_init_options(InitOps) -> - collect_init_options(lists:reverse(InitOps), state_functions, []). + collect_init_options(InitOps, state_functions, []). %% Keep the last of each kind collect_init_options([], CallbackMode, StateOps) -> - {CallbackMode,StateOps}; + {CallbackMode,lists:reverse(StateOps)}; collect_init_options([InitOp|InitOps] = IOIOs, CallbackMode, StateOps) -> case InitOp of {callback_mode,Mode} @@ -864,12 +864,11 @@ collect_init_options([InitOp|InitOps] = IOIOs, CallbackMode, StateOps) -> end. collect_state_options(StateOps) -> - collect_state_options( - lists:reverse(StateOps), false, false, undefined, []). + collect_state_options(StateOps, false, false, undefined, []). %% Keep the last of each kind collect_state_options( [], Retry, Hibernate, Timeout, Operations) -> - {Retry,Hibernate,Timeout,Operations}; + {Retry,Hibernate,Timeout,lists:reverse(Operations)}; collect_state_options( [StateOp|StateOps] = SOSOs, Retry, Hibernate, Timeout, Operations) -> case StateOp of @@ -909,7 +908,7 @@ process_state_operations([Operation|Operations] = OOs, Debug, S, Q, P) -> {reply,{_To,_Tag}=Client,Reply} -> NewDebug = do_reply(Debug, S, Client, Reply), process_state_operations(Operations, NewDebug, S, Q, P); - {insert_event,Type,Content} -> + {next_event,Type,Content} -> case event_type(Type) of true -> process_state_operations( diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 8a96f0e8e2..facc36fb9f 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -1180,7 +1180,7 @@ timeout(timeout, idle, idle, timeout, {From,Time}) -> TRefC2 = erlang:start_timer(Time, self(), cancel2), {next_state,timeout2,{From,Time,TRef2}, [{cancel_timer, TRefC1}, - {insert_event,internal,{cancel_timer,TRefC2}}]}; + {next_event,internal,{cancel_timer,TRefC2}}]}; timeout(_, _, _, State, Data) -> {next_state,State,Data}. |