From d30cae56d82763681b633cba25ad553eb672ec16 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Thu, 6 Apr 2017 16:09:38 +0200
Subject: Make Module:init/1 mandatory
---
lib/stdlib/doc/src/gen_statem.xml | 17 ++++++++++-------
lib/stdlib/src/gen_statem.erl | 3 +--
2 files changed, 11 insertions(+), 9 deletions(-)
(limited to 'lib')
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 5eb13db1aa..f7baaad5d1 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -146,7 +146,7 @@ erlang:'!' -----> Module:StateName/3
This gathers all code for a specific state
in one function as the gen_statem engine
branches depending on state name.
- Notice the fact that there is a mandatory callback function
+ Note the fact that the callback function
Module:terminate/3
makes the state name terminate unusable in this mode.
@@ -1704,7 +1704,7 @@ handle_event(_, _, State, Data) ->
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
From 30cae2492d8d8e927d57c0dc656ee2dfbec0a70c Mon Sep 17 00:00:00 2001
From: Raimo Niskanen
Date: Mon, 13 Feb 2017 08:20:16 +0100
Subject: Implement {timeout,Name} timeouts
---
lib/stdlib/doc/src/gen_statem.xml | 100 +++++++++++++++++++++++++++--------
lib/stdlib/src/gen_statem.erl | 79 +++++++++++++++------------
lib/stdlib/test/gen_statem_SUITE.erl | 70 +++++++++++++++++++++++-
3 files changed, 192 insertions(+), 57 deletions(-)
(limited to 'lib')
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 44ac1ad8ad..1b99b65e09 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -67,13 +67,16 @@
It has the same features and adds some really useful:
- - State code is gathered.
- - The state can be any term.
- - Events can be postponed.
- - Events can be self-generated.
- - Automatic state enter code can be called.
- - A reply can be sent from a later state.
- - There can be multiple sys traceable replies.
+ - Gathered state code.
+ - Arbitrary term state.
+ - Event postponing.
+ - Self-generated events.
+ - State time-out.
+ - Multiple generic named time-outs.
+ - Absolute time-out time.
+ - Automatic state enter calls.
+ - Reply from other state than the request.
+ - Multiple sys traceable replies.
The callback model(s) for gen_statem differs from
@@ -531,10 +534,12 @@ handle_event(_, _, State, Data) ->
originate from the corresponding API functions.
For calls, the event contains whom to reply to.
Type info originates from regular process messages sent
- to the gen_statem. Also, the state machine
- implementation can generate events of types
- timeout, state_timeout,
- and internal to itself.
+ to the gen_statem. The state machine
+ implementation can, in addition to the above,
+ generate
+ events of types
+ timeout, {timeout,Name},
+ state_timeout, and internal to itself.
@@ -701,13 +706,14 @@ handle_event(_, _, State, Data) ->
-
- Timeout timers
- state_timeout()
+ Time-out timers
+ event_timeout(),
+ generic_timeout()
and
- event_timeout()
+ state_timeout()
are handled. Time-outs with zero time are guaranteed to be
delivered to the state machine before any external
- not yet received event so if there is such a timeout requested,
+ not yet received event so if there is such a time-out requested,
the corresponding time-out zero event is enqueued as
the newest event.
@@ -795,8 +801,8 @@ handle_event(_, _, State, Data) ->
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.
+ are interpreted. Future erlang:start_timer/4 Options
+ will not necessarily be supported.
Any event that arrives cancels this time-out.
@@ -821,6 +827,42 @@ handle_event(_, _, State, Data) ->
+
+
+
+
+ Starts a timer set by
+ enter_action()
+ {timeout,Name}.
+ When the timer expires an event of
+ event_type()
+ {timeout,Name} will be generated.
+ See
+ erlang:start_timer/4
+ for how Time and
+ Options
+ are interpreted. Future erlang:start_timer/4 Options
+ will not necessarily be supported.
+
+
+ If Time is infinity,
+ no timer is started, as it never would expire anyway.
+
+
+ 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.
+
+
+ Setting a timer with the same Name while it is running
+ will restart it with the new time-out value.
+ Therefore it is possible to cancel
+ a specific time-out by setting it to infinity.
+
+
+
@@ -835,8 +877,8 @@ handle_event(_, _, State, Data) ->
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.
+ are interpreted. Future erlang:start_timer/4 Options
+ will not necessarily be supported.
If Time is infinity,
@@ -861,7 +903,7 @@ handle_event(_, _, State, Data) ->
If Abs is true an absolute timer is started,
- and if it false a relative, which is the default.
+ and if it is false a relative, which is the default.
See
erlang:start_timer/4
for details.
@@ -986,7 +1028,19 @@ handle_event(_, _, State, Data) ->
transition_option()
event_timeout()
to Time with EventContent
- and options
+ and time-out options
+ Options.
+
+
+ {timeout,Name}
+ -
+
+ Sets the
+ transition_option()
+ generic_timeout()
+ to Time for Name
+ with EventContent
+ and time-out options
Options.
@@ -997,7 +1051,7 @@ handle_event(_, _, State, Data) ->
transition_option()
state_timeout()
to Time with EventContent
- and options
+ and time-out options
Options.
@@ -1270,7 +1324,7 @@ handle_event(_, _, State, Data) ->
to avoid that the calling process dies when the call
times out, you will have to be prepared to handle
a late reply.
- So why not just allow the calling process to die?
+ So why not just let the calling process die?
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),
--
cgit v1.2.3
From eff1ee5ebf1d767d610cd6bc059e5f4dea57d2af Mon Sep 17 00:00:00 2001
From: Zandra Norman
Date: Mon, 23 Jan 2017 17:06:48 +0100
Subject: stdlib: Make gen_statem callbacks optional
---
lib/stdlib/doc/src/gen_statem.xml | 10 ++++
lib/stdlib/test/erl_internal_SUITE.erl | 9 ++-
lib/stdlib/test/gen_statem_SUITE.erl | 64 ++++++++++++++++++++--
.../test/gen_statem_SUITE_data/oc_statem.erl | 40 ++++++++++++++
4 files changed, 118 insertions(+), 5 deletions(-)
create mode 100644 lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
(limited to 'lib')
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 1b99b65e09..bc86415d28 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -1733,6 +1733,16 @@ handle_event(_, _, State, Data) ->
Reason = term()
+
+
+ This callback is optional, so callback modules need not export it.
+ If a release upgrade/downgrade with
+ Change={advanced,Extra}
+ specified in the .appup file is made
+ when code_change/4 is not implemented
+ the process will crash with exit reason undef.
+
+
This function is called by a gen_statem when it is to
update its internal state during a release upgrade/downgrade,
diff --git a/lib/stdlib/test/erl_internal_SUITE.erl b/lib/stdlib/test/erl_internal_SUITE.erl
index bfa48de6b7..e60c202a55 100644
--- a/lib/stdlib/test/erl_internal_SUITE.erl
+++ b/lib/stdlib/test/erl_internal_SUITE.erl
@@ -60,7 +60,7 @@ end_per_testcase(_Case, _Config) ->
%% Check that the behaviour callbacks are correctly defined.
behav(_) ->
Modules = [application, gen_server, gen_fsm, gen_event,
- supervisor_bridge, supervisor],
+ gen_statem, supervisor_bridge, supervisor],
lists:foreach(fun check_behav/1, Modules).
check_behav(Module) ->
@@ -89,6 +89,10 @@ callbacks(gen_event) ->
[{init,1}, {handle_event,2}, {handle_call,2},
{handle_info,2}, {terminate,2}, {code_change,3},
{format_status,2}];
+callbacks(gen_statem) ->
+ [{init, 1}, {callback_mode, 0}, {state_name, 3},
+ {handle_event, 4}, {terminate, 3}, {code_change, 4},
+ {format_status, 2}];
callbacks(supervisor_bridge) ->
[{init,1}, {terminate,2}];
callbacks(supervisor) ->
@@ -102,6 +106,9 @@ optional_callbacks(gen_fsm) ->
[{format_status,2}];
optional_callbacks(gen_event) ->
[{format_status,2}];
+optional_callbacks(gen_statem) ->
+ [{format_status, 2}, {state_name, 3},
+ {handle_event, 4}, {terminate, 3}, {code_change, 4}];
optional_callbacks(supervisor_bridge) ->
[];
optional_callbacks(supervisor) ->
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 36b1f761a7..cb045ca49f 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -40,7 +40,8 @@ all() ->
shutdown, stop_and_reply, state_enter, event_order,
state_timeout, event_types, generic_timers, code_change,
{group, sys},
- hibernate, enter_loop].
+ hibernate, enter_loop, {group, undef_callbacks},
+ undef_in_terminate].
groups() ->
[{start, [], tcs(start)},
@@ -50,7 +51,8 @@ groups() ->
{abnormal, [], tcs(abnormal)},
{abnormal_handle_event, [], tcs(abnormal)},
{sys, [], tcs(sys)},
- {sys_handle_event, [], tcs(sys)}].
+ {sys_handle_event, [], tcs(sys)},
+ {undef_callbacks, [], tcs(undef_callbacks)}].
tcs(start) ->
[start1, start2, start3, start4, start5, start6, start7,
@@ -62,8 +64,9 @@ tcs(abnormal) ->
tcs(sys) ->
[sys1, call_format_status,
error_format_status, terminate_crash_format,
- get_state, replace_state].
-
+ get_state, replace_state];
+tcs(undef_callbacks) ->
+ [undef_code_change, undef_terminate1, undef_terminate2].
init_per_suite(Config) ->
Config.
@@ -77,6 +80,11 @@ init_per_group(GroupName, Config)
GroupName =:= abnormal_handle_event;
GroupName =:= sys_handle_event ->
[{callback_mode,handle_event_function}|Config];
+init_per_group(undef_callbacks, Config) ->
+ DataDir = ?config(data_dir, Config),
+ StatemPath = filename:join(DataDir, "oc_statem.erl"),
+ {ok, oc_statem} = compile:file(StatemPath),
+ Config;
init_per_group(_GroupName, Config) ->
Config.
@@ -1461,6 +1469,51 @@ enter_loop(Reg1, Reg2) ->
gen_statem:enter_loop(?MODULE, [], state0, [])
end.
+undef_code_change(_Config) ->
+ {ok, Statem} = gen_statem:start(oc_statem, [], []),
+ {error, {'EXIT',
+ {undef, [{oc_statem, code_change, [_, _, _, _], _}|_]}}}
+ = fake_upgrade(Statem, oc_statem).
+
+fake_upgrade(Pid, Mod) ->
+ sys:suspend(Pid),
+ sys:replace_state(Pid, fun(State) -> {new, State} end),
+ Ret = sys:change_code(Pid, Mod, old_vsn, []),
+ ok = sys:resume(Pid),
+ Ret.
+
+undef_terminate1(_Config) ->
+ {ok, Statem} = gen_statem:start(oc_statem, [], []),
+ MRef = monitor(process, Statem),
+ ok = gen_statem:stop(Statem),
+ verify_down(Statem, MRef, normal),
+ ok.
+
+undef_terminate2(_Config) ->
+ Reason = {error, test},
+ {ok, Statem} = oc_statem:start(),
+ MRef = monitor(process, Statem),
+ ok = gen_statem:stop(Statem, Reason, infinity),
+ verify_down(Statem, MRef, Reason).
+
+undef_in_terminate(_Config) ->
+ Data = {undef_in_terminate, {?MODULE, terminate}},
+ {ok, Statem} = gen_statem:start(?MODULE, {data, Data}, []),
+ try
+ gen_statem:stop(Statem),
+ ct:fail(should_crash)
+ catch
+ exit:{undef, [{?MODULE, terminate, _, _}|_]} ->
+ ok
+ end.
+
+verify_down(Statem, MRef, Reason) ->
+ receive
+ {'DOWN', MRef, process, Statem, Reason} ->
+ ok
+ after 5000 ->
+ ct:fail(default_terminate_failed)
+ end.
%% Test the order for multiple {next_event,T,C}
next_events(Config) ->
@@ -1639,6 +1692,9 @@ callback_mode() ->
terminate(_, _State, crash_terminate) ->
exit({crash,terminate});
+terminate(_, _State, {undef_in_terminate, {Mod, Fun}}) ->
+ Mod:Fun(),
+ ok;
terminate({From,stopped}, State, _Data) ->
From ! {self(),{stopped,State}},
ok;
diff --git a/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
new file mode 100644
index 0000000000..577abe0520
--- /dev/null
+++ b/lib/stdlib/test/gen_statem_SUITE_data/oc_statem.erl
@@ -0,0 +1,40 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017. 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.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(oc_statem).
+
+-behaviour(gen_statem).
+
+%% API
+-export([start/0]).
+
+%% gen_statem callbacks
+-export([init/1, callback_mode/0]).
+
+start() ->
+ gen_statem:start({local, ?MODULE}, ?MODULE, [], []).
+
+init([]) ->
+ {ok, state_name, #{}}.
+
+callback_mode() ->
+ handle_event_function.
+
+
+
--
cgit v1.2.3