From bca4b5c87fd1aae2fdcb78b605181393a0caf9d9 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Tue, 7 Feb 2017 22:38:36 +0100
Subject: Implement erlang:start_timer opts
---
lib/stdlib/doc/src/gen_statem.xml | 75 ++++++++++++++-----
lib/stdlib/src/gen_statem.erl | 139 ++++++++++++++++++++++++++---------
lib/stdlib/test/gen_statem_SUITE.erl | 3 +-
3 files changed, 162 insertions(+), 55 deletions(-)
(limited to 'lib')
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index f7baaad5d1..44ac1ad8ad 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -785,28 +785,38 @@ handle_event(_, _, State, Data) ->
- Generates an event of
+ Starts a timer set by
+ enter_action()
+ timeout.
+ When the timer expires an event of
event_type()
- timeout
- after this time (in milliseconds) unless another
- event arrives or has arrived
- in which case this time-out is cancelled.
+ timeout will be generated.
+ See
+ erlang:start_timer/4
+ for how Time and
+ Options
+ are interpreted. All Options of erlang:start_timer/4
+ will not necessarily be supported in the future.
+
+
+ Any event that arrives cancels this time-out.
Note that a retried or inserted event counts as arrived.
So does a state time-out zero event, if it was generated
- before this timer is requested.
+ before this time-out is requested.
- If the value is infinity, no timer is started, as
- it never would trigger anyway.
+ If Time is infinity,
+ no timer is started, as it never would expire anyway.
- If the value is 0 no timer is actually started,
+ If Time is relative and 0
+ no timer is actually started,
instead the the time-out event is enqueued to ensure
that it gets processed before any not yet
received external event.
- Note that it is not possible or needed to cancel this time-out,
+ Note that it is not possible nor needed to cancel this time-out,
as it is cancelled automatically by any other event.
@@ -815,19 +825,26 @@ handle_event(_, _, State, Data) ->
- Generates an event of
+ Starts a timer set by
+ enter_action()
+ state_timeout.
+ When the timer expires an event of
event_type()
- state_timeout
- after this time (in milliseconds) unless the gen_statem
- changes states (NewState =/= OldState)
- which case this time-out is cancelled.
+ state_timeout will be generated.
+ See
+ erlang:start_timer/4
+ for how Time and
+ Options
+ are interpreted. All Options of erlang:start_timer/4
+ will not necessarily be supported in the future.
- If the value is infinity, no timer is started, as
- it never would trigger anyway.
+ If Time is infinity,
+ no timer is started, as it never would expire anyway.
- If the value is 0 no timer is actually started,
+ If Time is relative and 0
+ no timer is actually started,
instead the the time-out event is enqueued to ensure
that it gets processed before any not yet
received external event.
@@ -839,6 +856,20 @@ handle_event(_, _, State, Data) ->
+
+
+
+
+ If Abs is true an absolute timer is started,
+ and if it false a relative, which is the default.
+ See
+ erlang:start_timer/4
+ for details.
+
+
+
+
+
@@ -954,7 +985,9 @@ handle_event(_, _, State, Data) ->
Sets the
transition_option()
event_timeout()
- to Time with EventContent.
+ to Time with EventContent
+ and options
+ Options.
state_timeout
@@ -963,7 +996,9 @@ handle_event(_, _, State, Data) ->
Sets the
transition_option()
state_timeout()
- to Time with EventContent.
+ to Time with EventContent
+ and options
+ Options.
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 242ff87be7..fe80bb5de4 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -97,13 +97,14 @@
%% If 'true' hibernate the server instead of going into receive
boolean().
-type event_timeout() ::
- %% Generate a ('timeout', EventContent, ...) event after Time
+ %% Generate a ('timeout', EventContent, ...) event
%% unless some other event is delivered
- Time :: timeout().
+ Time :: timeout() | integer().
-type state_timeout() ::
- %% Generate a ('state_timeout', EventContent, ...) event after Time
+ %% Generate a ('state_timeout', EventContent, ...) event
%% unless the state is changed
- Time :: timeout().
+ Time :: timeout() | integer().
+-type timeout_option() :: {abs,Abs :: boolean()}.
-type action() ::
%% During a state change:
@@ -137,8 +138,16 @@
(Timeout :: event_timeout()) | % {timeout,Timeout}
{'timeout', % Set the event_timeout option
Time :: event_timeout(), EventContent :: term()} |
+ {'timeout', % Set the event_timeout option
+ Time :: event_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
+ Time :: state_timeout(),
+ EventContent :: term(),
+ Options :: (timeout_option() | [timeout_option()])} |
%%
reply_action().
-type reply_action() ::
@@ -1312,7 +1321,7 @@ parse_enter_actions(Debug, S, State, Actions, Hibernate, TimeoutsR) ->
parse_actions(Debug, S, State, Actions) ->
Hibernate = false,
- TimeoutsR = [{timeout,infinity,infinity}], %% Will cancel event timer
+ TimeoutsR = [infinity], %% Will cancel event timer
Postpone = false,
NextEventsR = [],
parse_actions(
@@ -1378,11 +1387,15 @@ parse_actions(
?STACKTRACE()}
end;
%%
- {state_timeout,_,_} = Timeout ->
+ {TimerType,_,_} = Timeout
+ when TimerType =:= timeout;
+ TimerType =:= state_timeout ->
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
- {timeout,_,_} = Timeout ->
+ {TimerType,_,_,_} = Timeout
+ when TimerType =:= timeout;
+ TimerType =:= state_timeout ->
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout);
@@ -1395,26 +1408,64 @@ parse_actions(
parse_actions_timeout(
Debug, S, State, Actions,
Hibernate, TimeoutsR, Postpone, NextEventsR, Timeout) ->
- Time =
- case Timeout of
- {_,T,_} -> T;
- T -> T
- end,
- case validate_time(Time) of
- true ->
- parse_actions(
- Debug, S, State, Actions,
- Hibernate, [Timeout|TimeoutsR],
- Postpone, NextEventsR);
- false ->
- {error,
- {bad_action_from_state_function,Timeout},
- ?STACKTRACE()}
+ case Timeout of
+ {TimerType,Time,TimerMsg,TimerOpts} ->
+ case validate_timer_args(Time, listify(TimerOpts)) of
+ true ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [Timeout|TimeoutsR],
+ Postpone, NextEventsR);
+ false ->
+ NewTimeout = {TimerType,Time,TimerMsg},
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [NewTimeout|TimeoutsR],
+ Postpone, NextEventsR);
+ error ->
+ {error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE()}
+ end;
+ {_,Time,_} ->
+ case validate_timer_args(Time, []) of
+ false ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [Timeout|TimeoutsR],
+ Postpone, NextEventsR);
+ error ->
+ {error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE()}
+ end;
+ Time ->
+ case validate_timer_args(Time, []) of
+ false ->
+ parse_actions(
+ Debug, S, State, Actions,
+ Hibernate, [Timeout|TimeoutsR],
+ Postpone, NextEventsR);
+ error ->
+ {error,
+ {bad_action_from_state_function,Timeout},
+ ?STACKTRACE()}
+ end
end.
-validate_time(Time) when is_integer(Time), Time >= 0 -> true;
-validate_time(infinity) -> true;
-validate_time(_) -> false.
+validate_timer_args(Time, Opts) ->
+ validate_timer_args(Time, Opts, false).
+%%
+validate_timer_args(Time, [], true) when is_integer(Time) ->
+ true;
+validate_timer_args(Time, [], false) when is_integer(Time), Time >= 0 ->
+ false;
+validate_timer_args(infinity, [], Abs) ->
+ Abs;
+validate_timer_args(Time, [{abs,Abs}|Opts], _) when is_boolean(Abs) ->
+ validate_timer_args(Time, Opts, Abs);
+validate_timer_args(_, [_|_], _) ->
+ error.
%% Stop and start timers as well as create timeout zero events
%% and pending event timer
@@ -1430,22 +1481,39 @@ parse_timers(
TimerRefs, TimerTypes, CancelTimers, [Timeout|TimeoutsR],
Seen, TimeoutEvents) ->
case Timeout of
+ {TimerType,Time,TimerMsg,TimerOpts} ->
+ %% Absolute timer
+ parse_timers(
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ TimerType, Time, TimerMsg, listify(TimerOpts));
+ %% Relative timers below
+ {TimerType,0,TimerMsg} ->
+ parse_timers(
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ TimerType, zero, TimerMsg, []);
{TimerType,Time,TimerMsg} ->
parse_timers(
- TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
- Seen, TimeoutEvents,
- TimerType, Time, TimerMsg);
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ TimerType, Time, TimerMsg, []);
+ 0 ->
+ parse_timers(
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ timeout, zero, 0, []);
Time ->
parse_timers(
- TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
- Seen, TimeoutEvents,
- timeout, Time, Time)
+ TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
+ Seen, TimeoutEvents,
+ timeout, Time, Time, [])
end.
parse_timers(
TimerRefs, TimerTypes, CancelTimers, TimeoutsR,
Seen, TimeoutEvents,
- TimerType, Time, TimerMsg) ->
+ TimerType, Time, TimerMsg, TimerOpts) ->
case Seen of
#{TimerType := _} ->
%% Type seen before - ignore
@@ -1464,7 +1532,7 @@ parse_timers(
parse_timers(
TimerRefs, NewTimerTypes, NewCancelTimers, TimeoutsR,
NewSeen, TimeoutEvents);
- 0 ->
+ zero ->
%% Cancel any running timer
{NewTimerTypes,NewCancelTimers} =
cancel_timer_by_type(
@@ -1477,7 +1545,8 @@ parse_timers(
_ ->
%% (Re)start the timer
TimerRef =
- erlang:start_timer(Time, self(), TimerMsg),
+ erlang:start_timer(
+ Time, self(), TimerMsg, TimerOpts),
case TimerTypes of
#{TimerType := OldTimerRef} ->
%% Cancel the running timer
@@ -1491,6 +1560,8 @@ parse_timers(
NewCancelTimers, TimeoutsR,
NewSeen, TimeoutEvents);
#{} ->
+ %% Insert the new timer into
+ %% both TimerRefs and TimerTypes
parse_timers(
TimerRefs#{TimerRef => TimerType},
TimerTypes#{TimerType => TimerRef},
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index ac27c9fc79..ee61099155 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1597,8 +1597,9 @@ idle({call,From}, {delayed_answer,T}, Data) ->
throw({keep_state,Data})
end;
idle({call,From}, {timeout,Time}, _Data) ->
+ AbsTime = erlang:monotonic_time(millisecond) + Time,
{next_state,timeout,{From,Time},
- {timeout,Time,idle}};
+ {timeout,AbsTime,idle,[{abs,true}]}};
idle(cast, next_event, _Data) ->
{next_state,next_events,[a,b,c],
[{next_event,internal,a},
--
cgit v1.2.3