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) -> Module:init(Args) -> Result(StateType) - Optional function for initializing process and internal state. + Initializing process and internal state. Args = term() @@ -1720,7 +1720,7 @@ handle_event(_, _, State, Data) -> start_link/3,4 or start/3,4, - this optional function is called by the new process to initialize + this function is called by the new process to initialize the implementation state and server data.

@@ -1729,13 +1729,16 @@ handle_event(_, _, State, Data) ->

- This callback is optional, so a callback module does not need - to export it, but most do. If this function is not exported, - the gen_statem should be started through + Note that if the gen_statem is started trough proc_lib and - enter_loop/4-6. + enter_loop/4-6, + this callback will never be called. + Since this callback is not optional it can + in that case be implemented as:

+
+init(Args) -> erlang:error(not_implemented, [Args]).
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index cacc932ec4..242ff87be7 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -287,8 +287,7 @@ StatusOption :: 'normal' | 'terminate'. -optional_callbacks( - [init/1, % One may use enter_loop/5,6,7 instead - format_status/2, % Has got a default implementation + [format_status/2, % Has got a default implementation terminate/3, % Has got a default implementation code_change/4, % Only needed by advanced soft upgrade %% -- cgit v1.2.3 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 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