From 30cae2492d8d8e927d57c0dc656ee2dfbec0a70c Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
The callback model(s) for
- Timeout timers
-
Any event that arrives cancels this time-out. @@ -821,6 +827,42 @@ handle_event(_, _, State, Data) ->
+
+ Starts a timer set by
+
+ If
+ If
+ Setting a timer with the same
If
If
+ Sets the
+
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index fe80bb5de4..6f566b8beb 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -78,8 +78,9 @@
-type data() :: term().
-type event_type() ::
- {'call',From :: from()} | 'cast' |
- 'info' | 'timeout' | 'state_timeout' | 'internal'.
+ {'call',From :: from()} | 'cast' | 'info' |
+ 'timeout' | {'timeout', Name :: term()} | 'state_timeout' |
+ 'internal'.
-type callback_mode_result() ::
callback_mode() | [callback_mode() | state_enter()].
@@ -88,7 +89,7 @@
-type transition_option() ::
postpone() | hibernate() |
- event_timeout() | state_timeout().
+ event_timeout() | generic_timeout() | state_timeout().
-type postpone() ::
%% If 'true' postpone the current event
%% and retry it when the state changes (=/=)
@@ -100,6 +101,9 @@
%% Generate a ('timeout', EventContent, ...) event
%% unless some other event is delivered
Time :: timeout() | integer().
+-type generic_timeout() ::
+ %% Generate a ({'timeout',Name}, EventContent, ...) event
+ Time :: timeout() | integer().
-type state_timeout() ::
%% Generate a ('state_timeout', EventContent, ...) event
%% unless the state is changed
@@ -142,6 +146,14 @@
Time :: event_timeout(),
EventContent :: term(),
Options :: (timeout_option() | [timeout_option()])} |
+ %%
+ {{'timeout', Name :: term()}, % Set the generic_timeout option
+ Time :: generic_timeout(), EventContent :: term()} |
+ {{'timeout', Name :: term()}, % Set the generic_timeout option
+ Time :: generic_timeout(),
+ EventContent :: term(),
+ Options :: (timeout_option() | [timeout_option()])} |
+ %%
{'state_timeout', % Set the state_timeout option
Time :: state_timeout(), EventContent :: term()} |
{'state_timeout', % Set the state_timeout option
@@ -311,37 +323,26 @@
%% Type validation functions
callback_mode(CallbackMode) ->
case CallbackMode of
- state_functions ->
- true;
- handle_event_function ->
- true;
- _ ->
- false
+ state_functions -> true;
+ handle_event_function -> true;
+ _ -> false
end.
%%
-from({Pid,_}) when is_pid(Pid) ->
- true;
-from(_) ->
- false.
+from({Pid,_}) when is_pid(Pid) -> true;
+from(_) -> false.
%%
event_type({call,From}) ->
from(From);
event_type(Type) ->
case Type of
- {call,From} ->
- from(From);
- cast ->
- true;
- info ->
- true;
- timeout ->
- true;
- state_timeout ->
- true;
- internal ->
- true;
- _ ->
- false
+ {call,From} -> from(From);
+ cast -> true;
+ info -> true;
+ timeout -> true;
+ state_timeout -> true;
+ internal -> true;
+ {timeout,_} -> true;
+ _ -> false
end.
@@ -1387,15 +1388,27 @@ parse_actions(
?STACKTRACE()}
end;
%%
- {TimerType,_,_} = Timeout
- when TimerType =:= timeout;
- TimerType =:= state_timeout ->
+ {{timeout,_},_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {{timeout,_},_,_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {timeout,_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {timeout,_,_,_} = Timeout ->
+ parse_actions_timeout(
+ Debug, S, State, Actions,
+ Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
+ {state_timeout,_,_} = Timeout ->
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
- {TimerType,_,_,_} = Timeout
- when TimerType =:= timeout;
- TimerType =:= state_timeout ->
+ {state_timeout,_,_,_} = Timeout ->
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index ee61099155..36b1f761a7 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -38,7 +38,7 @@ all() ->
{group, abnormal},
{group, abnormal_handle_event},
shutdown, stop_and_reply, state_enter, event_order,
- state_timeout, event_types, code_change,
+ state_timeout, event_types, generic_timers, code_change,
{group, sys},
hibernate, enter_loop].
@@ -834,6 +834,7 @@ event_types(_Config) ->
{next_event,timeout,3},
{next_event,info,4},
{next_event,cast,5},
+ {next_event,{timeout,6}, 6},
{next_event,Call,Req}]}
end,
state1 =>
@@ -857,6 +858,10 @@ event_types(_Config) ->
{next_state, state6, undefined}
end,
state6 =>
+ fun ({timeout,6}, 6, undefined) ->
+ {next_state, state7, undefined}
+ end,
+ state7 =>
fun ({call,From}, stop, undefined) ->
{stop_and_reply, shutdown,
[{reply,From,stopped}]}
@@ -884,6 +889,69 @@ event_types(_Config) ->
+generic_timers(_Config) ->
+ process_flag(trap_exit, true),
+
+ Machine =
+ %% Abusing the internal format of From...
+ #{init =>
+ fun () ->
+ {ok, start, undefined}
+ end,
+ start =>
+ fun ({call,_} = Call, Req, undefined) ->
+ {next_state, state1, undefined,
+ [{{timeout,a},1500,1},
+ {state_timeout,1500,1},
+ {{timeout,b},1000,1},
+ {next_event,Call,Req}]}
+ end,
+ state1 =>
+ fun ({call,_} = Call, Req, undefined) ->
+ T = erlang:monotonic_time(millisecond) + 500,
+ {next_state, state2, undefined,
+ [{{timeout,c},T,2,{abs,true}},
+ {{timeout,d},0,2,[{abs,false}]},
+ {timeout,0,2},
+ {{timeout,b},infinity,2},
+ {{timeout,a},1000,{Call,Req}}]}
+ end,
+ state2 =>
+ fun ({timeout,d}, 2, undefined) ->
+ {next_state, state3, undefined}
+ end,
+ state3 =>
+ fun ({timeout,c}, 2, undefined) ->
+ {next_state, state4, undefined}
+ end,
+ state4 =>
+ fun ({timeout,a}, {{call,From},stop}, undefined) ->
+ {stop_and_reply, shutdown,
+ [{reply,From,stopped}]}
+ end},
+ {ok,STM} =
+ gen_statem:start_link(
+ ?MODULE, {map_statem,Machine,[]}, [{debug,[trace]}]),
+
+ stopped = gen_statem:call(STM, stop),
+ receive
+ {'EXIT',STM,shutdown} ->
+ ok
+ after 500 ->
+ ct:fail(did_not_stop)
+ end,
+
+ {noproc,_} =
+ ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason),
+ case flush() of
+ [] ->
+ ok;
+ Other2 ->
+ ct:fail({unexpected,Other2})
+ end.
+
+
+
sys1(Config) ->
{ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []),
{status, Pid, {module,gen_statem}, _} = sys:get_status(Pid),
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index f4d84ab163..f01615fdcd 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -4,7 +4,7 @@
- A timeout feature inherited from
- Whenever we receive a button event we start an event timeout
+ Whenever we receive a button event we start an event time-out
of 30 seconds, and if we get an event type
- An event timeout is cancelled by any other event so you either - get some other event or the timeout event. It is therefore - not possible nor needed to cancel or restart an event timeout. + 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 timeout... + the event time-out...
The previous example of state time-outs only work if
the state machine stays in the same state during the
@@ -934,13 +950,68 @@ locked(
You may want to start a timer in one state and respond
to the time-out in another, maybe cancel the time-out
without changing states, or perhaps run multiple
- time-outs in parallel. All this can be accomplished
- with Erlang Timers:
+ time-outs in parallel. All this can be accomplished with
+
+ Here is how to accomplish the state time-out
+ in the previous example by instead using a generic time-out
+ named
+ case Remaining of
+ [Digit] ->
+ do_unlock(),
+ {next_state, open, Data#{remaining := Code},
+ [{{timeout,open_tm},10000,lock}]};
+...
+
+open({timeout,open_tm}, lock, Data) ->
+ do_lock(),
+ {next_state,locked,Data};
+open(cast, {button,_}, Data) ->
+ {keep_state,Data};
+...
+ ]]>
+
+ Just as
+
+ Another way to handle a late time-out can be to not cancel it, + but to ignore it if it arrives in a state + where it is known to be late. +
+
+ The most versatile way to handle time-outs is to use
+ Erlang Timers; see
Here is how to accomplish the state time-out - in the previous example by insted using an Erlang Timer: + in the previous example by instead using an Erlang Timer:
{keep_state_and_data,
- [{reply,From,length(Code)}]};
+ [{reply,From,length(Code)}]};
%%
%% State: locked
handle_event(
@@ -1636,7 +1707,7 @@ handle_event(
if
Digit =:= LockButton ->
{next_state, {locked,LockButton}, Data,
- [{reply,From,locked}]);
+ [{reply,From,locked}]};
true ->
{keep_state_and_data,
[postpone]}
@@ -1710,10 +1781,10 @@ handle_event(
EventType, EventContent,
{open,LockButton}, Data) ->
case {EventType, EventContent} of
- {enter, _OldState} ->
- do_unlock(),
- {keep_state_and_data,
- [{state_timeout,10000,lock},hibernate]};
+ {enter, _OldState} ->
+ do_unlock(),
+ {keep_state_and_data,
+ [{state_timeout,10000,lock},hibernate]};
...
]]>
-- cgit v1.2.3