diff options
author | Raimo Niskanen <raimo@erlang.org> | 2019-08-23 09:42:59 +0200 |
---|---|---|
committer | Raimo Niskanen <raimo@erlang.org> | 2019-08-29 15:46:35 +0200 |
commit | f515415944a7952786e130034b735bdf473df91a (patch) | |
tree | 95d3c87354772df567ede482ebe4cd386d9e1e07 | |
parent | 8f6b49452953499d26fc784cc0fde035119ca75a (diff) | |
download | erlang-f515415944a7952786e130034b735bdf473df91a.tar.gz |
Implement timeout cancel and update
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 78 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 227 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 107 | ||||
-rw-r--r-- | system/doc/design_principles/statem.xml | 85 |
4 files changed, 397 insertions, 100 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index ef548ad643..423da60804 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> @@ -1176,7 +1194,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> @@ -1229,6 +1247,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/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 7e3f93d535..64cc0cd602 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()}. @@ -1525,20 +1535,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, @@ -1555,14 +1565,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}, @@ -1587,10 +1596,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, @@ -1609,15 +1620,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( @@ -1760,7 +1796,7 @@ loop_state_change( %% case TimeoutTypes of %% Optimization - %% - only cancel timeout when there is an active timeout + %% - only cancel timeout when it is active %% #{state_timeout := TimerRef} -> %% State timeout active @@ -1789,7 +1825,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, @@ -1797,7 +1833,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( @@ -1822,7 +1858,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, @@ -1830,6 +1867,8 @@ loop_timeouts( NextEventsR, Hibernate, [], Postponed, Timers, _Seen, TimeoutEvents) -> %% + %% End of timeouts + %% S_1 = S#state{ state_data = NextState_NewData, @@ -1865,37 +1904,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, @@ -1903,8 +1933,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, @@ -1931,13 +1963,20 @@ 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), case Debug of ?not_sys_debug -> - loop_timeouts( + loop_timeouts_register( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, Timers, Seen, TimeoutEvents, @@ -1950,15 +1989,18 @@ loop_timeouts( {start_timer, {TimeoutType,Time,TimeoutMsg,TimeoutOpts}, State}), - loop_timeouts( + loop_timeouts_register( P, Debug_1, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, Timers, Seen, TimeoutEvents, TimeoutType, TimerRef) end end. + +%% Loop helper to register a newly started timer +%% and to cancel any running timer %% -loop_timeouts( +loop_timeouts_register( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, {TimerRefs, TimeoutTypes}, Seen, TimeoutEvents, @@ -1980,7 +2022,7 @@ loop_timeouts( P, Debug, S, Events, NextState_NewData, NextEventsR, Hibernate, TimeoutsR, Postponed, - Timers, Seen#{TimeoutType => true}, TimeoutEvents); + Timers, Seen#{TimeoutType => true}, TimeoutEvents); #{} -> %% Insert the new timer type and ref Timers = @@ -2033,6 +2075,79 @@ loop_timeouts_cancel( Timers, Seen#{TimeoutType => true}, TimeoutEvents) end. +%% Loop helper to update the timeout message by cancelling +%% the old timer and restarting with the remaining time and new message +%% +loop_timeouts_update( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + {TimerRefs,TimeoutTypes}, Seen, TimeoutEvents, + TimeoutType, TimeoutMsg) -> + %% + case TimeoutTypes of + #{TimeoutType := OldTimerRef} -> + case erlang:cancel_timer(OldTimerRef) of + false -> + %% Already expired - insert timeout event with new msg + receive + {timeout,OldTimerRef,_} -> + ok + end, + Timers = + {maps:remove(OldTimerRef, TimerRefs), + maps:remove(TimeoutType, TimeoutTypes)}, + TimeoutEvents_1 = + [{TimeoutType,TimeoutMsg}|TimeoutEvents], + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, TimeoutEvents_1); + Time -> + %% Start a new timer for the remaining time + TimerRef = + erlang:start_timer(Time, self(), TimeoutMsg), + %% Insert the new timer type and ref + Timers = + {maps:remove( + OldTimerRef, + TimerRefs#{TimerRef => TimeoutType}), + TimeoutTypes#{TimeoutType := TimerRef}}, + case Debug of + ?not_sys_debug -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, TimeoutEvents); + _ -> + {State,_Data} = S#state.state_data, + Debug_1 = + sys_debug( + Debug, P#params.name, + {start_timer, + {TimeoutType,Time,TimeoutMsg,[]}, + State}), + loop_timeouts( + P, Debug_1, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, TimeoutEvents) + end + end; + #{} -> + Timers = {TimerRefs,TimeoutTypes}, + %% Not running - insert timeout event + 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 %% @@ -2094,13 +2209,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. %% diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 3f92bfa4d7..31808a915f 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -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, @@ -875,7 +879,6 @@ state_timeout(_Config) -> {ok,STM} = gen_statem:start_link( ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]), - sys:trace(STM, true), TRef = erlang:start_timer(1000, self(), kull), ok = gen_statem:call(STM, {go,500}), ok = gen_statem:call(STM, check), @@ -901,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), @@ -1895,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 23e9054547..b2d769d3a9 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -561,34 +561,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, EventContent, Time, Opts}</c> + <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 /> + <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> @@ -844,9 +858,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 @@ -869,6 +883,32 @@ 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 <em>transition action</em> 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 <em>transition action</em> 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" /> @@ -1200,10 +1240,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> @@ -1472,12 +1514,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 @@ -1548,6 +1591,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> <!-- =================================================================== --> |