summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaimo Niskanen <raimo@erlang.org>2019-09-09 14:34:26 +0200
committerRaimo Niskanen <raimo@erlang.org>2019-09-09 14:34:26 +0200
commit9dbdeb852c54a4633c4a899c7be198d4bb8aa3ba (patch)
tree6778f52bd6950a675a84ade03cb676cf54318a34
parent253349fd37db625a287e276ee1fc32bbb6638076 (diff)
parentbe7de82343a71a4e5e30606fdcb34697218c4c77 (diff)
downloaderlang-9dbdeb852c54a4633c4a899c7be198d4bb8aa3ba.tar.gz
Merge branch 'raimo/stdlib/gen_statem-improve-timers/OTP-15510' into maint
* raimo/stdlib/gen_statem-improve-timers/OTP-15510: Update documentation after review Change data structure for Timers Implement timeout cancel and update Improve sys:log of timeouts Log time-outs in crash and get_status
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml78
-rw-r--r--lib/stdlib/doc/src/sys.xml28
-rw-r--r--lib/stdlib/src/gen_statem.erl408
-rw-r--r--lib/stdlib/src/sys.erl2
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl116
-rw-r--r--system/doc/design_principles/statem.xml146
6 files changed, 570 insertions, 208 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 5513b7d649..0e35bd3db3 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -57,19 +57,37 @@
containing detailed facts that may rot by age.
</p>
<note>
- <p>
- This behavior appeared in Erlang/OTP 19.0.
- In OTP 19.1 a backwards incompatible change of
- the return tuple from
- <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
- was made and the mandatory callback function
- <seealso marker="#Module:callback_mode/0">
- <c>Module:callback_mode/0</c>
- </seealso>
- was introduced. In OTP 20.0 the
- <seealso marker="#type-generic_timeout"><c>generic timeouts</c></seealso>
- were added.
- </p>
+ <list type="bulleted">
+ <item>This behavior appeared in Erlang/OTP 19.0.</item>
+ <item>
+ In OTP 19.1 a backwards incompatible change of
+ the return tuple from
+ <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
+ was made and the mandatory callback function
+ <seealso marker="#Module:callback_mode/0">
+ <c>Module:callback_mode/0</c>
+ </seealso>
+ was introduced.
+ </item>
+ <item>
+ In OTP 20.0
+ <seealso marker="#type-generic_timeout">
+ generic time-outs
+ </seealso>
+ were added.
+ </item>
+ <item>
+ In OTP 22.1 time-out content
+ <seealso marker="#type-timeout_update_action">
+ <c>update</c>
+ </seealso>
+ and explicit time-out
+ <seealso marker="#type-timeout_cancel_action">
+ <c>cancel</c>
+ </seealso>
+ were added.
+ </item>
+ </list>
</note>
<p>
<c>gen_statem</c> has got the same features that
@@ -653,7 +671,7 @@ handle_event(_, _, State, Data) ->
<name name="timeout_event_type"/>
<desc>
<p>
- There are 3 types of timeout events that the state machine
+ There are 3 types of time-out events that the state machine
can generate for itself with the corresponding
<seealso marker="#type-timeout_action">timeout_action()</seealso>s.
</p>
@@ -1189,7 +1207,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>.
</p>
<p>
- These timeout actions sets timeout
+ These time-out actions sets time-out
<seealso marker="#type-transition_option">transition options</seealso>.
</p>
<taglist>
@@ -1242,6 +1260,36 @@ handle_event(_, _, State, Data) ->
</desc>
</datatype>
<datatype>
+ <name name="timeout_cancel_action" since="OTP @OTP-15510@"/>
+ <desc>
+ <p>
+ This is a shorter and clearer form of
+ <seealso marker="#type-timeout_action">
+ timeout_action()
+ </seealso>
+ with <c>Time = infinity</c> which cancels a time-out.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
+ <name name="timeout_update_action" since="OTP @OTP-15510@"/>
+ <desc>
+ <p>
+ Updates a time-out with a new <c>EventContent</c>.
+ See
+ <seealso marker="#type-timeout_action">
+ timeout_action()
+ </seealso>
+ for how to start a time-out.
+ </p>
+ <p>
+ If no time-out of the same type is active instead
+ insert the time-out event just like when starting
+ a time-out with relative <c>Time = 0</c>.
+ </p>
+ </desc>
+ </datatype>
+ <datatype>
<name name="reply_action"/>
<desc>
<p>
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
index e22ca89ef5..ebea054fff 100644
--- a/lib/stdlib/doc/src/sys.xml
+++ b/lib/stdlib/doc/src/sys.xml
@@ -239,6 +239,34 @@
</item>
<tag>
<c>
+ {start_timer,<anno>Action</anno>,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c>
+ when the action <c><anno>Action</anno></c>
+ starts a timer in state <c><anno>State</anno></c>.
+ </p>
+ </item>
+ <tag>
+ <c>
+ {insert_timeout,<anno>Event</anno>,<anno>State</anno>}
+ </c>
+ </tag>
+ <item>
+ <p>
+ Is produced by <c>gen_statem</c> when a timeout zero action
+ inserts event <c><anno>Event</anno></c>
+ in state <c><anno>State</anno></c>.
+ </p>
+ <p>
+ <c><anno>Event</anno></c> is
+ an <c>{EventType,EventContent}</c> tuple.
+ </p>
+ </item>
+ <tag>
+ <c>
{enter,<anno>State</anno>}
</c>
</tag>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 49911eac2c..ed25799cc2 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -176,7 +176,17 @@
{'state_timeout', % Set the state_timeout option
Time :: state_timeout(),
EventContent :: term(),
- Options :: (timeout_option() | [timeout_option()])}.
+ Options :: (timeout_option() | [timeout_option()])} |
+ timeout_cancel_action() |
+ timeout_update_action().
+-type timeout_cancel_action() ::
+ {'timeout', 'cancel'} |
+ {{'timeout', Name :: term()}, 'cancel'} |
+ {'state_timeout', 'cancel'}.
+-type timeout_update_action() ::
+ {'timeout', 'update', EventContent :: term()} |
+ {{'timeout', Name :: term()}, 'update', EventContent :: term()} |
+ {'state_timeout', 'update', EventContent :: term()}.
-type reply_action() ::
{'reply', % Reply to a caller
From :: from(), Reply :: term()}.
@@ -420,11 +430,9 @@ timeout_event_type(Type) ->
{state_data = {undefined,undefined} ::
{State :: term(),Data :: term()},
postponed = [] :: [{event_type(),term()}],
- timers = {#{},#{}} ::
- {%% TimerRef => TimeoutType
- TimerRefs :: #{reference() => timeout_event_type()},
- %% TimeoutType => TimerRef
- TimeoutTypes :: #{timeout_event_type() => reference()}},
+ timers = #{} ::
+ #{TimeoutType :: timeout_event_type() =>
+ {TimerRef :: reference(), TimeoutMsg :: term()}},
hibernate = false :: boolean()
}).
@@ -807,13 +815,14 @@ format_status(
Opt,
[PDict,SysState,Parent,Debug,
{#params{name = Name} = P,
- #state{postponed = Postponed} = S}]) ->
+ #state{postponed = Postponed, timers = Timers} = S}]) ->
Header = gen:format_status_header("Status for state machine", Name),
Log = sys:get_log(Debug),
[{header,Header},
{data,
[{"Status",SysState},
{"Parent",Parent},
+ {"Time-outs",list_timeouts(Timers)},
{"Logged Events",Log},
{"Postponed",Postponed}]} |
case format_status(Opt, PDict, update_parent(P, Parent), S) of
@@ -860,6 +869,14 @@ print_event(Dev, SystemEvent, Name) ->
io:format(
Dev, "*DBG* ~tp enter in state ~tp~n",
[Name,State]);
+ {start_timer,Action,State} ->
+ io:format(
+ Dev, "*DBG* ~tp start_timer ~tp in state ~tp~n",
+ [Name,Action,State]);
+ {insert_timeout,Event,State} ->
+ io:format(
+ Dev, "*DBG* ~tp insert_timeout ~tp in state ~tp~n",
+ [Name,Event,State]);
{terminate,Reason,State} ->
io:format(
Dev, "*DBG* ~tp terminate ~tp in state ~tp~n",
@@ -945,15 +962,12 @@ loop_receive(
{'$gen_cast',Cast} ->
loop_receive_result(P, Debug, S, {cast,Cast});
%%
- {timeout,TimerRef,TimeoutMsg} ->
- {TimerRefs,TimeoutTypes} = S#state.timers,
- case TimerRefs of
- #{TimerRef := TimeoutType} ->
+ {timeout,TimerRef,TimeoutType} ->
+ case S#state.timers of
+ #{TimeoutType := {TimerRef,TimeoutMsg}} = Timers ->
%% Our timer
- Timers =
- {maps:remove(TimerRef, TimerRefs),
- maps:remove(TimeoutType, TimeoutTypes)},
- S_1 = S#state{timers = Timers},
+ Timers_1 = maps:remove(TimeoutType, Timers),
+ S_1 = S#state{timers = Timers_1},
loop_receive_result(
P, Debug, S_1, {TimeoutType,TimeoutMsg});
#{} ->
@@ -1514,20 +1528,20 @@ loop_actions_timeout(
true ->
case listify(TimeoutOpts) of
%% Optimization cases
- [] when ?relative_timeout(Time) ->
- RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
+ [{abs,true}] when ?absolute_timeout(Time) ->
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
- [RelativeTimeout|TimeoutsR], Postpone,
+ [Timeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
- [{abs,true}] when ?absolute_timeout(Time) ->
+ [{abs,false}] when ?relative_timeout(Time) ->
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
- [Timeout|TimeoutsR], Postpone,
+ [RelativeTimeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
- [{abs,false}] when ?relative_timeout(Time) ->
+ [] when ?relative_timeout(Time) ->
RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
@@ -1544,14 +1558,13 @@ loop_actions_timeout(
[Timeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
false when ?relative_timeout(Time) ->
- RelativeTimeout =
- {TimeoutType,Time,TimeoutMsg},
+ RelativeTimeout = {TimeoutType,Time,TimeoutMsg},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
[RelativeTimeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
- badarg ->
+ _ ->
terminate(
error,
{bad_action_from_state_function,Timeout},
@@ -1576,10 +1589,12 @@ loop_actions_timeout(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
CallEnter, StateCall, Actions,
- {TimeoutType,Time,_} = Timeout) ->
+ {TimeoutType,Time,_TimeoutMsg} = Timeout) ->
%%
case timeout_event_type(TimeoutType) of
- true when ?relative_timeout(Time) ->
+ true
+ when ?relative_timeout(Time);
+ Time =:= update ->
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
@@ -1598,15 +1613,40 @@ loop_actions_timeout(
loop_actions_timeout(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postpone,
- CallEnter, StateCall, Actions, Time) ->
+ CallEnter, StateCall, Actions,
+ {TimeoutType,cancel} = Action) ->
+ %%
+ case timeout_event_type(TimeoutType) of
+ true ->
+ Timeout = {TimeoutType,infinity,undefined},
+ loop_actions_list(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate,
+ [Timeout|TimeoutsR], Postpone,
+ CallEnter, StateCall, Actions);
+ false ->
+ terminate(
+ error,
+ {bad_action_from_state_function,Action},
+ ?STACKTRACE(), P, Debug,
+ S#state{
+ state_data = NextState_NewData,
+ hibernate = Hibernate},
+ Q)
+ end;
+loop_actions_timeout(
+ P, Debug, S, Q, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postpone,
+ CallEnter, StateCall, Actions,
+ Time) ->
%%
if
?relative_timeout(Time) ->
- RelativeTimeout = {timeout,Time,Time},
+ Timeout = {timeout,Time,Time},
loop_actions_list(
P, Debug, S, Q, NextState_NewData,
NextEventsR, Hibernate,
- [RelativeTimeout|TimeoutsR], Postpone,
+ [Timeout|TimeoutsR], Postpone,
CallEnter, StateCall, Actions);
true ->
terminate(
@@ -1683,23 +1723,20 @@ loop_state_transition(
%% State transition to the same state
%%
loop_keep_state(
- P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ P, Debug, #state{timers = Timers} = S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed) ->
%%
%% Cancel event timeout
%%
- case TimeoutTypes of
- %% Optimization
- %% - only cancel timer when there is a timer to cancel
- #{timeout := TimerRef} ->
+ case Timers of
+ #{timeout := {TimerRef,_TimeoutMsg}} ->
%% Event timeout active
loop_next_events(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- cancel_timer_by_ref_and_type(
- TimerRef, timeout, TimerRefs, TimeoutTypes));
+ cancel_timer(timeout, TimerRef, Timers));
_ ->
%% No event timeout active
loop_next_events(
@@ -1741,34 +1778,32 @@ loop_state_change(
end.
%%
loop_state_change(
- P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S,
+ P, Debug, #state{timers = Timers} = S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR) ->
%%
%% Cancel state and event timeout
%%
- case TimeoutTypes of
+ case Timers of
%% Optimization
- %% - only cancel timeout when there is an active timeout
+ %% - only cancel timeout when it is active
%%
- #{state_timeout := TimerRef} ->
+ #{state_timeout := {TimerRef,_TimeoutMsg}} ->
%% State timeout active
%% - cancel event timeout too since it is faster than inspecting
loop_next_events(
P, Debug, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, [],
- cancel_timer_by_type(
+ cancel_timer(
timeout,
- cancel_timer_by_ref_and_type(
- TimerRef, state_timeout, TimerRefs, TimeoutTypes)));
- #{timeout := TimerRef} ->
+ cancel_timer(state_timeout, TimerRef, Timers)));
+ #{timeout := {TimerRef,_TimeoutMsg}} ->
%% Event timeout active but not state timeout
%% - cancel event timeout only
loop_next_events(
P, Debug, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, [],
- cancel_timer_by_ref_and_type(
- TimerRef, timeout, TimerRefs, TimeoutTypes));
+ cancel_timer(timeout, TimerRef, Timers));
_ ->
%% No state nor event timeout active.
loop_next_events(
@@ -1778,7 +1813,7 @@ loop_state_change(
end.
%% Continue state transition with processing of
-%% inserted events and timeout events
+%% timeouts and inserted events
%%
loop_next_events(
P, Debug, S,
@@ -1786,7 +1821,7 @@ loop_next_events(
NextEventsR, Hibernate, [], Postponed,
Timers) ->
%%
- %% Optimization when there are no timeout actions
+ %% Optimization when there are no timeouts
%% hence no timeout zero events to append to Events
%% - avoid loop_timeouts
loop_done(
@@ -1811,7 +1846,8 @@ loop_next_events(
NextEventsR, Hibernate, TimeoutsR, Postponed,
Timers, Seen, TimeoutEvents).
-%% Continue state transition with processing of timeout events
+%% Continue state transition with processing of timeouts
+%% and finally inserted events
%%
loop_timeouts(
P, Debug, S,
@@ -1819,6 +1855,8 @@ loop_timeouts(
NextEventsR, Hibernate, [], Postponed,
Timers, _Seen, TimeoutEvents) ->
%%
+ %% End of timeouts
+ %%
S_1 =
S#state{
state_data = NextState_NewData,
@@ -1854,37 +1892,28 @@ loop_timeouts(
NextEventsR, Hibernate, [Timeout|TimeoutsR], Postponed,
Timers, Seen, TimeoutEvents) ->
%%
- case Timeout of
- {TimeoutType,Time,TimeoutMsg} ->
- %% Relative timeout
- case Seen of
- #{TimeoutType := _} ->
- %% Type seen before - ignore
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers, Seen, TimeoutEvents);
- #{} ->
- loop_timeouts(
+ TimeoutType = element(1, Timeout),
+ case Seen of
+ #{TimeoutType := _} ->
+ %% Type seen before - ignore
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents);
+ #{} ->
+ case Timeout of
+ {_,Time,TimeoutMsg} ->
+ %% Relative timeout or update
+ loop_timeouts_start(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
Timers, Seen, TimeoutEvents,
- TimeoutType, Time, TimeoutMsg, [])
- end;
- {TimeoutType,Time,TimeoutMsg,TimeoutOpts} ->
- %% Absolute timeout
- case Seen of
- #{TimeoutType := _} ->
- %% Type seen before - ignore
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
- NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers, Seen, TimeoutEvents);
- #{} ->
- loop_timeouts(
+ TimeoutType, Time, TimeoutMsg, []);
+ {_,Time,TimeoutMsg,TimeoutOpts} ->
+ %% Absolute timeout
+ loop_timeouts_start(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
@@ -1892,8 +1921,10 @@ loop_timeouts(
TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts))
end
end.
+
+%% Loop helper to start or restart a timeout
%%
-loop_timeouts(
+loop_timeouts_start(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
@@ -1920,51 +1951,79 @@ loop_timeouts(
NextEventsR, Hibernate, TimeoutsR, Postponed,
Timers, Seen, [{TimeoutType,TimeoutMsg}|TimeoutEvents],
TimeoutType);
+ update ->
+ loop_timeouts_update(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimeoutMsg);
_ ->
%% (Re)start the timer
TimerRef =
- erlang:start_timer(Time, self(), TimeoutMsg, TimeoutOpts),
- {TimerRefs,TimeoutTypes} = Timers,
- case TimeoutTypes of
- #{TimeoutType := OldTimerRef} ->
- %% Cancel the running timer,
- %% update the timeout type,
- %% insert the new timer ref,
- %% and remove the old timer ref
- Timers_1 =
- {maps:remove(
- OldTimerRef,
- TimerRefs#{TimerRef => TimeoutType}),
- TimeoutTypes#{TimeoutType := TimerRef}},
- cancel_timer(OldTimerRef),
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
+ erlang:start_timer(Time, self(), TimeoutType, TimeoutOpts),
+ case Debug of
+ ?not_sys_debug ->
+ loop_timeouts_register(
+ P, Debug, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
- #{} ->
- %% Insert the new timer type and ref
- Timers_1 =
- {TimerRefs#{TimerRef => TimeoutType},
- TimeoutTypes#{TimeoutType => TimerRef}},
- loop_timeouts(
- P, Debug, S,
- Events, NextState_NewData,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimerRef, TimeoutMsg);
+ _ ->
+ {State,_Data} = NextState_NewData,
+ Debug_1 =
+ sys_debug(
+ Debug, P#params.name,
+ {start_timer,
+ {TimeoutType,Time,TimeoutMsg,TimeoutOpts},
+ State}),
+ loop_timeouts_register(
+ P, Debug_1, S, Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- Timers_1, Seen#{TimeoutType => true}, TimeoutEvents)
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimerRef, TimeoutMsg)
end
end.
+%% Loop helper to register a newly started timer
+%% and to cancel any running timer
+%%
+loop_timeouts_register(
+ P, Debug, S, Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimerRef, TimeoutMsg) ->
+ %%
+ case Timers of
+ #{TimeoutType := {OldTimerRef,_OldTimeoutMsg}} ->
+ %% Cancel the running timer,
+ %% and update timer type and ref
+ cancel_timer(OldTimerRef),
+ Timers_1 = Timers#{TimeoutType := {TimerRef,TimeoutMsg}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents);
+ #{} ->
+ %% Insert the new timer type and ref
+ Timers_1 = Timers#{TimeoutType => {TimerRef,TimeoutMsg}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true}, TimeoutEvents)
+ end.
+
%% Loop helper to cancel a timeout
%%
loop_timeouts_cancel(
P, Debug, S,
Events, NextState_NewData,
NextEventsR, Hibernate, TimeoutsR, Postponed,
- {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents,
- TimeoutType) ->
+ Timers, Seen, TimeoutEvents, TimeoutType) ->
%% This function body should have been:
- %% Timers_1 = cancel_timer_by_type(TimeoutType, Timers),
+ %% Timers_1 = cancel_timer(TimeoutType, Timers),
%% loop_timeouts(
%% P, Debug, S,
%% Events, NextState_NewData,
@@ -1974,14 +2033,12 @@ loop_timeouts_cancel(
%% Explicitly separate cases to get separate code paths for when
%% the map key exists vs. not, since otherwise the external call
%% to erlang:cancel_timer/1 and to map:remove/2 within
- %% cancel_timer_by_type/2 would cause all live registers
+ %% cancel_timer/2 would cause all live registers
%% to be saved to and restored from the stack also for
%% the case when the map key TimeoutType does not exist
- case TimeoutTypes of
- #{TimeoutType := TimerRef} ->
- Timers_1 =
- cancel_timer_by_ref_and_type(
- TimerRef, TimeoutType, TimerRefs, TimeoutTypes),
+ case Timers of
+ #{TimeoutType := {TimerRef,_TimeoutMsg}} ->
+ Timers_1 = cancel_timer(TimeoutType, TimerRef, Timers),
loop_timeouts(
P, Debug, S,
Events, NextState_NewData,
@@ -1995,6 +2052,36 @@ loop_timeouts_cancel(
Timers, Seen#{TimeoutType => true}, TimeoutEvents)
end.
+%% Loop helper to update the timeout message,
+%% or insert an event if no timer is running
+%%
+loop_timeouts_update(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen, TimeoutEvents,
+ TimeoutType, TimeoutMsg) ->
+ %%
+ case Timers of
+ #{TimeoutType := {TimerRef,_OldTimeoutMsg}} ->
+ Timers_1 = Timers#{TimeoutType := {TimerRef,TimeoutMsg}},
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers_1, Seen#{TimeoutType => true},
+ TimeoutEvents);
+ #{} ->
+ TimeoutEvents_1 =
+ [{TimeoutType,TimeoutMsg}|TimeoutEvents],
+ loop_timeouts(
+ P, Debug, S,
+ Events, NextState_NewData,
+ NextEventsR, Hibernate, TimeoutsR, Postponed,
+ Timers, Seen#{TimeoutType => true},
+ TimeoutEvents_1)
+ end.
+
%% Continue state transition with prepending timeout zero events
%% before event queue reversal i.e appending timeout zero events
%%
@@ -2056,13 +2143,13 @@ parse_timeout_opts_abs(Opts, Abs) ->
%% Enqueue immediate timeout events (timeout 0 events)
%%
-%% Event timer timeout 0 events gets special treatment since
-%% an event timer is cancelled by any received event,
-%% so if there are enqueued events before the event timer
-%% timeout 0 event - the event timer is cancelled hence no event.
+%% Event timeout 0 events gets special treatment since
+%% an event timeout is cancelled by any received event,
+%% so if there are enqueued events before the event
+%% timeout 0 event - the event timeout is cancelled hence no event.
%%
%% Other (state_timeout and {timeout,Name}) timeout 0 events
-%% that are after an event timer timeout 0 event are considered to
+%% that occur after an event timer timeout 0 event are considered to
%% belong to timers that were started after the event timer
%% timeout 0 event fired, so they do not cancel the event timer.
%%
@@ -2079,7 +2166,8 @@ prepend_timeout_events(
{State,_Data} = S#state.state_data,
Debug_1 =
sys_debug(
- Debug, P#params.name, {in,TimeoutEvent,State}),
+ Debug, P#params.name,
+ {insert_timeout,TimeoutEvent,State}),
prepend_timeout_events(
P, Debug_1, S, TimeoutEvents, [TimeoutEvent])
end;
@@ -2099,7 +2187,8 @@ prepend_timeout_events(
{State,_Data} = S#state.state_data,
Debug_1 =
sys_debug(
- Debug, P#params.name, {in,TimeoutEvent,State}),
+ Debug, P#params.name,
+ {insert_timeout,TimeoutEvent,State}),
prepend_timeout_events(
P, Debug_1, S, TimeoutEvents, [TimeoutEvent|EventsR])
end.
@@ -2190,7 +2279,9 @@ error_info(
name = Name,
callback_mode = CallbackMode,
state_enter = StateEnter} = P,
- #state{postponed = Postponed} = S,
+ #state{
+ postponed = Postponed,
+ timers = Timers} = S,
Q) ->
Log = sys:get_log(Debug),
?LOG_ERROR(#{label=>{gen_statem,terminate},
@@ -2200,6 +2291,7 @@ error_info(
callback_mode=>CallbackMode,
state_enter=>StateEnter,
state=>format_status(terminate, get(), P, S),
+ timeouts=>list_timeouts(Timers),
log=>Log,
reason=>{Class,Reason,Stacktrace},
client_info=>client_stacktrace(Q)},
@@ -2238,6 +2330,7 @@ format_log(#{label:={gen_statem,terminate},
callback_mode:=CallbackMode,
state_enter:=StateEnter,
state:=FmtData,
+ timeouts:=Timeouts,
log:=Log,
reason:={Class,Reason,Stacktrace},
client_info:=ClientInfo}) ->
@@ -2293,6 +2386,10 @@ format_log(#{label:={gen_statem,terminate},
[] -> "";
_ -> "** Stacktrace =~n** ~tp~n"
end ++
+ case Timeouts of
+ {0,_} -> "";
+ _ -> "** Time-outs: ~p~n"
+ end ++
case Log of
[] -> "";
_ -> "** Log =~n** ~tp~n"
@@ -2317,6 +2414,10 @@ format_log(#{label:={gen_statem,terminate},
[] -> [];
_ -> [error_logger:limit_term(FixedStacktrace)]
end ++
+ case Timeouts of
+ {0,_} -> [];
+ _ -> [error_logger:limit_term(Timeouts)]
+ end ++
case Log of
[] -> [];
_ -> [[error_logger:limit_term(T) || T <- Log]]
@@ -2370,40 +2471,55 @@ listify(Item) ->
[Item].
--define(cancel_timer(TimerRef),
- case erlang:cancel_timer(TimerRef) of
- false ->
- %% No timer found and we have not seen the timeout message
- receive
- {timeout,(TimerRef),_} ->
- ok
- end;
- _ ->
- %% Timer was running
- ok
- end).
-
+-define(
+ cancel_timer(TimerRef),
+ case erlang:cancel_timer(TimerRef) of
+ false ->
+ %% No timer found and we have not seen the timeout message
+ receive
+ {timeout,(TimerRef),_} ->
+ ok
+ end;
+ _ ->
+ %% Timer was running
+ ok
+ end).
+%%
+%% Cancel timer and consume timeout message
+%%
-compile({inline, [cancel_timer/1]}).
cancel_timer(TimerRef) ->
?cancel_timer(TimerRef).
+-define(
+ cancel_timer(TimeoutType, TimerRef, Timers),
+ begin
+ ?cancel_timer(TimerRef),
+ maps:remove(begin TimeoutType end, begin Timers end)
+ end).
+%%
+%% Cancel timer and remove from Timers
+%%
+-compile({inline, [cancel_timer/3]}).
+cancel_timer(TimeoutType, TimerRef, Timers) ->
+ ?cancel_timer(TimeoutType, TimerRef, Timers).
+
%% Cancel timer if running, otherwise no op
%%
%% Remove the timer from Timers
--compile({inline, [cancel_timer_by_type/2]}).
-cancel_timer_by_type(TimeoutType, {TimerRefs,TimeoutTypes} = Timers) ->
- case TimeoutTypes of
+-compile({inline, [cancel_timer/2]}).
+cancel_timer(TimeoutType, Timers) ->
+ case Timers of
#{TimeoutType := TimerRef} ->
- ?cancel_timer(TimerRef),
- {maps:remove(TimerRef, TimerRefs),
- maps:remove(TimeoutType, TimeoutTypes)};
+ ?cancel_timer(TimeoutType, TimerRef, Timers);
#{} ->
Timers
end.
--compile({inline, [cancel_timer_by_ref_and_type/4]}).
-cancel_timer_by_ref_and_type(
- TimerRef, TimeoutType, TimerRefs, TimeoutTypes) ->
- ?cancel_timer(TimerRef),
- {maps:remove(TimerRef, TimerRefs),
- maps:remove(TimeoutType, TimeoutTypes)}.
+%% Return a list of all pending timeouts
+list_timeouts(Timers) ->
+ {maps:size(Timers),
+ maps:fold(
+ fun(TimeoutType, {_TimerRef,TimeoutMsg}, Acc) ->
+ [{TimeoutType,TimeoutMsg}|Acc]
+ end, [], Timers)}.
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
index 6ff9aa33b4..93bf4743d2 100644
--- a/lib/stdlib/src/sys.erl
+++ b/lib/stdlib/src/sys.erl
@@ -51,6 +51,8 @@
| {'code_change', Event :: _, State :: _}
| {'postpone', Event :: _, State :: _, NextState :: _}
| {'consume', Event :: _, State :: _, NextState :: _}
+ | {'start_timer', Action :: _, State :: _}
+ | {'insert_timeout', Event :: _, State :: _}
| {'enter', State :: _}
| {'terminate', Reason :: _, State :: _}
| term().
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 16cf8f43f9..31808a915f 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2016-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2016-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
suite() ->
[{ct_hooks,[ts_install_cth]},
- {timetrap,{minutes,1}}].
+ {timetrap,{seconds,10}}].
all() ->
[{group, start},
@@ -38,7 +38,8 @@ all() ->
{group, abnormal},
{group, abnormal_handle_event},
shutdown, stop_and_reply, state_enter, event_order,
- state_timeout, event_types, generic_timers, code_change,
+ state_timeout, timeout_cancel_and_update,
+ event_types, generic_timers, code_change,
{group, sys},
hibernate, auto_hibernate, enter_loop, {group, undef_callbacks},
undef_in_terminate].
@@ -518,10 +519,11 @@ abnormal2(Config) ->
?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
- {{{bad_return_from_state_function,badreturn},_},_} =
+ Cause = bad_return_from_state_function,
+ {{{Cause,badreturn},_},_} =
?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason),
receive
- {'EXIT',Pid,{{bad_return_from_state_function,badreturn},_}} -> ok
+ {'EXIT',Pid,{{Cause,badreturn},_}} -> ok
after 5000 ->
ct:fail(gen_statem_did_not_die)
end,
@@ -538,10 +540,11 @@ abnormal3(Config) ->
?MODULE, start_arg(Config, []), [{debug,[log]}]),
%% bad return value in the gen_statem loop
- {{{bad_action_from_state_function,badaction},_},_} =
+ Cause = bad_action_from_state_function,
+ {{{Cause,badaction},_},_} =
?EXPECT_FAILURE(gen_statem:call(Pid, badaction), Reason),
receive
- {'EXIT',Pid,{{bad_action_from_state_function,badaction},_}} -> ok
+ {'EXIT',Pid,{{Cause,badaction},_}} -> ok
after 5000 ->
ct:fail(gen_statem_did_not_die)
end,
@@ -559,10 +562,11 @@ abnormal4(Config) ->
%% bad return value in the gen_statem loop
BadTimeout = {badtimeout,4711,ouch},
- {{{bad_action_from_state_function,BadTimeout},_},_} =
- ?EXPECT_FAILURE(gen_statem:call(Pid, BadTimeout), Reason),
+ Cause = bad_action_from_state_function,
+ {{{Cause,BadTimeout},_},_} =
+ ?EXPECT_FAILURE(gen_statem:call(Pid, {badtimeout,BadTimeout}), Reason),
receive
- {'EXIT',Pid,{{bad_action_from_state_function,BadTimeout},_}} -> ok
+ {'EXIT',Pid,{{Cause,BadTimeout},_}} -> ok
after 5000 ->
ct:fail(gen_statem_did_not_die)
end,
@@ -872,8 +876,9 @@ state_timeout(_Config) ->
{reply,From,ok}}
end},
- {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine,[]}, []),
- sys:trace(STM, true),
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]),
TRef = erlang:start_timer(1000, self(), kull),
ok = gen_statem:call(STM, {go,500}),
ok = gen_statem:call(STM, check),
@@ -899,6 +904,88 @@ state_timeout(_Config) ->
+timeout_cancel_and_update(_Config) ->
+ process_flag(trap_exit, true),
+ %%
+ Machine =
+ #{init =>
+ fun () ->
+ {ok,start,0}
+ end,
+ start =>
+ fun
+ ({call,From}, test, 0) ->
+ self() ! message_to_self,
+ {next_state, state1, From,
+ %% Verify that internal events goes before external
+ [{state_timeout,17,1},
+ {next_event,internal,1}]}
+ end,
+ state1 =>
+ fun
+ (internal, 1, _) ->
+ {keep_state_and_data,
+ [{state_timeout,cancel},
+ {{timeout,a},17,1}]};
+ (info, message_to_self, _) ->
+ {keep_state_and_data,
+ [{{timeout,a},update,a}]};
+ ({timeout,a}, a, Data) ->
+ {next_state,state2,Data,
+ [{state_timeout,17,2},
+ {next_event,internal,2}]}
+ end,
+ state2 =>
+ fun
+ (internal, 2, _) ->
+ receive after 50 -> ok end,
+ %% Now state_timeout 17 should have triggered
+ {keep_state_and_data,
+ [{state_timeout,update,b},
+ {timeout,17,2}]};
+ (state_timeout, b, From) ->
+ {next_state,state3,3,
+ [{reply,From,ok},
+ 17000]}
+ end,
+ state3 =>
+ fun
+ ({call,From}, stop, 3) ->
+ {stop_and_reply, normal,
+ [{reply,From,ok}]}
+ end
+ },
+ %%
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]),
+ ok = gen_statem:call(STM, test),
+ {status, STM, {module,gen_statem}, Info} = sys:get_status(STM),
+ ct:log("Status info: ~p~n", [Info]),
+ {_,Timeouts} = dig_data_tuple(Info),
+ {_, {1,[{timeout,17000}]}} = lists:keyfind("Time-outs", 1, Timeouts),
+ %%
+ ok = gen_statem:call(STM, stop),
+ receive
+ {'EXIT',STM,normal} ->
+ ok
+ after 500 ->
+ ct:fail(did_not_stop)
+ end,
+ %%
+ verify_empty_msgq().
+
+dig_data_tuple([{data,_} = DataTuple|_]) -> DataTuple;
+dig_data_tuple([H|T]) when is_list(H) ->
+ case dig_data_tuple(H) of
+ false -> dig_data_tuple(T);
+ DataTuple -> DataTuple
+ end;
+dig_data_tuple([_|T]) -> dig_data_tuple(T);
+dig_data_tuple([]) -> false.
+
+
+
%% Test that all event types can be sent with {next_event,EventType,_}
event_types(_Config) ->
process_flag(trap_exit, true),
@@ -1042,7 +1129,8 @@ generic_timers(_Config) ->
sys1(Config) ->
{ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
- {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid),
+ {status, Pid, {module,gen_statem}, Info} = sys:get_status(Pid),
+ ct:log("Status info: ~p~n", [Info]),
sys:suspend(Pid),
Parent = self(),
Tag = make_ref(),
@@ -1892,7 +1980,7 @@ idle({call,_From}, badreturn, _Data) ->
badreturn;
idle({call,_From}, badaction, Data) ->
{keep_state, Data, [badaction]};
-idle({call,_From}, {badtimeout,_,_} = BadTimeout, Data) ->
+idle({call,_From}, {badtimeout,BadTimeout}, Data) ->
{keep_state, Data, BadTimeout};
idle({call,From}, {delayed_answer,T}, Data) ->
receive
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index 81a0c617f1..360bf958ad 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -108,7 +108,9 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
Co-located callback code for each state,
for all
- <seealso marker="#Event Types"><em>Event Types</em></seealso>
+ <seealso marker="#Event Types and Event Content">
+ <em>Event Types</em>
+ </seealso>
(such as <em>call</em>, <em>cast</em> and <em>info</em>)
</item>
<item>
@@ -136,7 +138,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
(<seealso marker="#State Time-Outs">State Time-Outs</seealso>,
<seealso marker="#Event Time-Outs">Event Time-Outs</seealso>
and
- <seealso marker="#Generic Time-Outs">Generic Time-outs</seealso>
+ <seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>
(named time-outs))
</item>
</list>
@@ -353,9 +355,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</taglist>
<p>
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>,
+ The other arguments are the <c>EventType</c>
+ and the event dependent <c>EventContent</c>,
+ both described in section
+ <seealso marker="#Event Types and Event Content">Event Types and Event Content</seealso>,
and the current server <c>Data</c>.
</p>
<p>
@@ -561,34 +564,48 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-state_timeout">
- <c>{state_timeout, EventContent, Time}</c>
+ <c>{state_timeout, Time, EventContent}</c>
+ </seealso>
+ <br />
+ <c>{state_timeout, Time, EventContent, Opts}</c><br />
+ <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <c>{state_timeout, update, EventContent}</c>
</seealso>
<br />
- <c>{state_timeout, EventContent, Time, Opts}</c>
+ <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <c>{state_timeout, cancel}</c>
+ </seealso>
</tag>
<item>
- Start a state time-out, read more in sections
+ Start, update or cancel a state time-out, read more in sections
<seealso marker="#Time-Outs">Time-Outs</seealso> and
<seealso marker="#State Time-Outs">State Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-generic_timeout">
- <c>{{timeout, Name}, EventContent, Time}</c>
+ <c>{{timeout, Name}, Time, EventContent}</c>
+ </seealso>
+ <br />
+ <c>{{timeout, Name}, Time, EventContent, Opts}</c><br />
+ <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <c>{{timeout, Name}, update, EventContent}</c>
</seealso>
<br />
- <c>{{timeout, Name}, EventContent, Time, Opts}</c>
+ <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <c>{{timeout, Name}, cancel}</c>
+ </seealso>
</tag>
<item>
- Start a generic time-out, read more in sections
+ Start, update or cancel a generic time-out, read more in sections
<seealso marker="#Time-Outs">Time-Outs</seealso> and
<seealso marker="#Generic Time-Outs">Generic Time-Outs</seealso>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-event_timeout">
- <c>{timeout, EventContent, Time}</c>
+ <c>{timeout, Time, EventContent}</c>
</seealso>
<br />
- <c>{timeout, EventContent, Time, Opts}</c><br />
+ <c>{timeout, Time, EventContent, Opts}</c><br />
<c>Time</c>
</tag>
<item>
@@ -636,19 +653,30 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
in the <c>gen_statem(3)</c> manual page for type
<seealso marker="stdlib:gen_statem#type-transition_option"><c>transition_option()</c></seealso>.
</p>
+ <p>
+ The different
+ <seealso marker="#Time-Outs">Time-Outs</seealso> and
+ <seealso marker="#Inserted Events"><c>next_event</c></seealso>
+ actions generate new events with corresponding
+ <seealso marker="#Event Types and Event Content">
+ Event Types and Event Content
+ </seealso>.
+ </p>
</section>
<!-- =================================================================== -->
<section>
- <marker id="Event Types" />
- <title>Event Types</title>
+ <marker id="Event Types and Event Content" />
+ <title>Event Types and Event Content</title>
<p>
Events are categorized in different
<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.
+ The meaning of the <c>EventContent</c>
+ depends on the <c>EventType</c>.
</p>
<p>
The following is a complete list of <em>event types</em> and where
@@ -662,7 +690,10 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Generated by
- <seealso marker="stdlib:gen_statem#cast/2"><c>gen_statem:cast</c></seealso>.
+ <seealso marker="stdlib:gen_statem#cast/2">
+ <c>gen_statem:cast(ServerRef, Msg)</c>
+ </seealso>
+ where <c>Msg</c> becomes the <c>EventContent</c>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-external_event_type">
@@ -671,11 +702,17 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
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
+ <seealso marker="stdlib:gen_statem#call/2">
+ <c>gen_statem:call(ServerRef, Request)</c>
+ </seealso>
+ where <c>Request</c> becomes the <c>EventContent</c>.
+ <c>From</c> is the reply address to use
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>.
+ <c>{reply,From,Reply}</c> or by calling
+ <seealso marker="stdlib:gen_statem#reply/1">
+ <c>gen_statem:reply(From, Reply)</c>
+ </seealso>
+ from the <em>callback module</em>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-external_event_type">
@@ -685,6 +722,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
<item>
Generated by any regular process message sent to
the <c>gen_statem</c> process.
+ The process message becomes the <c>EventContent</c>.
</item>
<tag>
<seealso marker="stdlib:gen_statem#type-timeout_event_type">
@@ -736,7 +774,7 @@ State(S) x Event(E) -> Actions(A), State(S')</pre>
</tag>
<item>
Generated by <em>transition action</em>
- <seealso marker="stdlib:gen_statem#type-enter_action"><c>{next_event,internal,EventContent}</c></seealso>.
+ <seealso marker="stdlib:gen_statem#type-action"><c>{next_event,internal,EventContent}</c></seealso>.
All <em>event types</em> above can also be generated using
the <c>next_event</c> action:
<c>{next_event,EventType,EventContent}</c>.
@@ -805,7 +843,7 @@ StateName(EventType, EventContent, Data) ->
<section>
<marker id="Time-Outs" />
- <title>Time-outs</title>
+ <title>Time-Outs</title>
<p>
Time-outs in <c>gen_statem</c> are started from a
<seealso marker="#Transition Actions">
@@ -856,9 +894,9 @@ StateName(EventType, EventContent, Data) ->
</item>
</taglist>
<p>
- When a time-out is started any running time-out with the same tag,
+ When a time-out is started any running time-out of the same type;
<c>state_timeout</c>, <c>{timeout, Name}</c> or <c>timeout</c>,
- is cancelled, that is the time-out is restarted with the new time.
+ is cancelled, that is, the time-out is restarted with the new time.
</p>
<p>
All time-outs has got an <c>EventContent</c> that is part of the
@@ -881,6 +919,39 @@ StateName(EventType, EventContent, Data) ->
The <c>EventContent</c> will in this case be ignored,
so why not set it to <c>undefined</c>.
</p>
+ <p>
+ A more explicit way to cancel a timer is to use a
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
+ on the form
+ <seealso marker="stdlib:gen_statem#type-timeout_cancel_action">
+ <c>{TimeoutType, cancel}</c>
+ </seealso>
+ which is a feature introduced in OTP 22.1.
+ </p>
+ </section>
+ <section>
+ <marker id="Updating a Time-Out" />
+ <title>Updating a Time-Out</title>
+ <p>
+ While a time-out is running, its <c>EventContent</c>
+ can be updated using a
+ <seealso marker="#Transition Actions">
+ <em>transition action</em>
+ </seealso>
+ on the form
+ <seealso marker="stdlib:gen_statem#type-timeout_update_action">
+ <c>{TimeoutType, update, NewEventContent}</c>
+ </seealso>
+ which is a feature introduced in OTP 22.1.
+ </p>
+ <p>
+ If this feature is used while no such <c>TimeoutType</c>
+ is running then a time-out event is immediately delivered
+ as when starting a
+ <seealso marker="#Time-Out Zero">Time-Out Zero</seealso>.
+ </p>
</section>
<section>
<marker id="Time-Out Zero" />
@@ -1212,10 +1283,12 @@ open(state_timeout, lock, Data) ->
<p>
The timer for a state time-out is automatically cancelled
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>
+ <p>
+ You can restart, cancel or update a state time-out.
+ See section
+ <seealso marker="#Time-Outs">Time-Outs</seealso>
+ for details.
</p>
</section>
@@ -1484,12 +1557,13 @@ locked(
<p>
An event time-out is cancelled by any other event so you either
get some other event or the time-out event. It is therefore
- not possible nor needed to cancel or restart an event time-out.
- Whatever event you act on has already cancelled
- the event time-out...
+ not possible nor needed to cancel, restart or update an event time-out.
+ Whatever event you act on has already cancelled the event time-out,
+ so there is never a running event time-out
+ while the <em>state callback</em> executes.
</p>
<p>
- Note that an event time-out does not work well with
+ Note that an event time-out does not work well
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
@@ -1560,6 +1634,12 @@ open(cast, {button,_}, Data) ->
a late time-out event can be handled by ignoring it
if it arrives in a state where it is known to be late.
</p>
+ <p>
+ You can restart, cancel or update a generic time-out.
+ See section
+ <seealso marker="#Time-Outs">Time-Outs</seealso>
+ for details.
+ </p>
</section>
<!-- =================================================================== -->
@@ -1870,7 +1950,7 @@ open(state_timeout, lock, Data) ->
<seealso marker="#Transition Actions">
<em>transition action</em>
</seealso>
- <c>{next_event,EventType,EventContent}</c>.
+ <seealso marker="stdlib:gen_statem#type-action"><c>{next_event,EventType,EventContent}</c></seealso>.
</p>
<p>
You can generate events of any existing