From 1958b93b4aa90883be5102d465f67f167549dea9 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Wed, 24 Feb 2016 09:41:34 +0100 Subject: Ditch State so StateName/5 -> StateName/4 --- lib/stdlib/doc/src/gen_statem.xml | 64 ++++++++------- lib/stdlib/src/gen_statem.erl | 36 +++++--- lib/stdlib/test/gen_statem_SUITE.erl | 155 +++++++++++++++++------------------ 3 files changed, 136 insertions(+), 119 deletions(-) diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 096be3025e..8934d912c6 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -62,7 +62,7 @@ gen_statem:stop -----> Module:terminate/2 gen_statem:call gen_statem:cast erlang:send -erlang:'!' -----> Module:StateName/5 +erlang:'!' -----> Module:StateName/4 Module:handle_event/5 - -----> Module:terminate/3 @@ -84,18 +84,28 @@ erlang:'!' -----> Module:StateName/5 in a gen_statem is the callback function that is called for all events in this state, and is selected depending on callback_mode - that the implementation selects during gen_statem init. + that the implementation specifies during gen_statem init.

When callback_mode is state_functions the state has to be an atom and is used as the state function name. See - - Module:StateName/5 + + Module:StateName/4 . This gathers all code for a specific state in one function and hence dispatches on state first. + Note that in this mode the callback function + + Module:code_change/4 + makes the state name code_change + unusable. Actually you might get away with using it + as a state name but you will have to find a way to separate + the two callback roles e.g by observing that you can force + argument 3 into having different types for the different roles. + This is a slippery slope, though. It is easier + to simply not use code_change as a state name.

When callback_mode @@ -110,13 +120,11 @@ erlang:'!' -----> Module:StateName/5 states so you do not accidentally postpone one event forever creating an infinite busy loop.

-

Any state name or any state value (depending on - callback_mode) - is permitted with a small gotcha regarding the state - undefined that is used as the previous state when - the first gen_statem state function is called. - You might need to know about this faked state if you - inspect the previous state argument in your state functions. +

There is a small gotcha in both callback modes regarding + the state undefined that is used as the previous state + when the first state function is called. So either do not + use undefined as a state, or figure out + the implications yourself.

The gen_statem enqueues incoming events in order of arrival and presents these to the @@ -181,7 +189,7 @@ erlang:'!' -----> Module:StateName/5 for a long time. However use this feature with care since hibernation implies at least two garbage collections (when hibernating and shortly after waking up) and that is not - something you'd want to do between each event on a busy server. + something you would want to do between each event on a busy server.

@@ -350,13 +358,20 @@ erlang:'!' -----> Module:StateName/5 +

+ The callback_mode is selected when starting the + gen_statem using the return value from + Module:init/1 + or when calling + enter_loop/5-7. +

state_functions The state has to be of type state_name() and one callback function per state that is - - Module:StateName/5 + + Module:StateName/4 is used. This is the default. handle_event_function @@ -384,7 +399,7 @@ erlang:'!' -----> Module:StateName/5 state function, from Module:init/1 or given to - enter_loop/5,6. + enter_loop/6,7.

The processing order for a state change is:

@@ -990,7 +1005,7 @@ erlang:'!' -----> Module:StateName/5 Module:StateName(EventType, EventContent, - PrevStateName, StateName, Data) -> Result + PrevStateName, Data) -> Result Module:handle_event(EventType, EventContent, PrevState, State, Data) -> Result @@ -1004,9 +1019,6 @@ erlang:'!' -----> Module:StateName/5 PrevStateName = state_name() - StateName = - state_name() - PrevState = State = state() @@ -1023,10 +1035,9 @@ erlang:'!' -----> Module:StateName/5

Whenever a gen_statem receives an event from call/2, cast/2 or - as a normal process message this function is called. - If - callback_mode - is state_functions then Module:StateName/5 is called, + as a normal process message one of these functions is called. + If callback_mode + is state_functions then Module:StateName/4 is called, and if it is handle_event_function then Module:handle_event/5 is called.

@@ -1043,13 +1054,10 @@ erlang:'!' -----> Module:StateName/5 reply(Caller, Reply) .

-

StateName is useful in some odd cases for example - if you call a common event handling function from your state - function then you might want to pass StateName. -

PrevStateName and PrevState are useful in some odd cases for example when you want to do something - only at the first event in a state. + only at the first event in a state, or when you need to + handle events differently depending on the previous state. Note that when gen_statem enters its first state this is set to undefined.

diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 02c8d60c61..fe84a428f6 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -152,8 +152,7 @@ -callback state_name( event_type(), EventContent :: term(), - PrevStateName :: state_name() | reference(), - StateName :: state_name(), % Current state + PrevStateName :: state_name(), % Previous state name Data :: data()) -> state_callback_result(). %% @@ -164,7 +163,7 @@ -callback handle_event( event_type(), EventContent :: term(), - PrevState :: state(), + PrevState :: state(), % Previous state State :: state(), % Current state Data :: data()) -> state_callback_result(). @@ -203,7 +202,7 @@ [init/1, % One may use enter_loop/5,6,7 instead format_status/2, % Has got a default implementation %% - state_name/5, % Example for callback_mode =:= state_functions: + state_name/4, % Example for callback_mode =:= state_functions: %% there has to be a StateName/5 callback function for every StateName. %% handle_event/5]). % For callback_mode =:= handle_event_function @@ -740,14 +739,13 @@ loop_events( [{Type,Content} = Event|Events] = Q, Timer) -> _ = (Timer =/= undefined) andalso cancel_timer(Timer), - Func = + try case CallbackMode of - handle_event_function -> - handle_event; state_functions -> - State - end, - try Module:Func(Type, Content, PrevState, State, Data) of + Module:State(Type, Content, PrevState, Data); + handle_event_function -> + Module:handle_event(Type, Content, PrevState, State, Data) + end of Result -> loop_event_result( Parent, Debug, S, Events, Event, Result) @@ -759,15 +757,27 @@ loop_events( %% Process an undef to check for the simple mistake %% of calling a nonexistent state function case erlang:get_stacktrace() of - [{Module,Func, - [Type,Content,PrevState,State,Data]=Args, + [{Module,State, + [Type,Content,PrevState,Data]=Args, _} - |Stacktrace] -> + |Stacktrace] + when CallbackMode =:= state_functions -> terminate( error, {undef_state_function,{Module,State,Args}}, Stacktrace, Debug, S, Q); + [{Module,handle_event, + [Type,Content,PrevState,State,Data]=Args, + _} + |Stacktrace] + when CallbackMode =:= handle_event_function -> + terminate( + error, + {undef_state_function, + {Module,handle_event,Args}}, + Stacktrace, + Debug, S, Q); Stacktrace -> terminate(error, undef, Stacktrace, Debug, S, Q) end; diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 81182eff71..3302171329 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -1160,73 +1160,73 @@ terminate(_Reason, _State, _Data) -> %% State functions -idle(cast, {connect,Pid}, _, _, Data) -> +idle(cast, {connect,Pid}, _, Data) -> Pid ! accept, {next_state,wfor_conf,Data}; -idle({call,From}, connect, _, _, Data) -> +idle({call,From}, connect, _, Data) -> gen_statem:reply(From, accept), {next_state,wfor_conf,Data}; -idle(cast, badreturn, _, _, _Data) -> +idle(cast, badreturn, _, _Data) -> badreturn; -idle({call,_From}, badreturn, _, _, _Data) -> +idle({call,_From}, badreturn, _, _Data) -> badreturn; -idle({call,From}, {delayed_answer,T}, _, _, Data) -> +idle({call,From}, {delayed_answer,T}, _, Data) -> receive after T -> gen_statem:reply({reply,From,delayed}), throw({keep_state,Data}) end; -idle({call,From}, {timeout,Time}, _, State, _Data) -> +idle({call,From}, {timeout,Time}, _, _Data) -> {next_state,timeout,{From,Time}, - [{timeout,Time,State}]}; -idle(Type, Content, PrevState, State, Data) -> - case handle_common_events(Type, Content, PrevState, State, Data) of + [{timeout,Time,idle}]}; +idle(Type, Content, PrevState, Data) -> + case handle_common_events(Type, Content, idle, Data) of undefined -> case Type of {call,From} -> throw({keep_state,Data,[{reply,From,'eh?'}]}); _ -> throw( - {stop,{unexpected,State,PrevState,Type,Content}}) + {stop,{unexpected,idle,Type,Content,PrevState}}) end; Result -> Result end. -timeout(timeout, idle, idle, timeout, {From,Time}) -> +timeout(timeout, idle, idle, {From,Time}) -> TRef2 = erlang:start_timer(Time, self(), ok), TRefC1 = erlang:start_timer(Time, self(), cancel1), TRefC2 = erlang:start_timer(Time, self(), cancel2), {next_state,timeout2,{From,Time,TRef2}, [{cancel_timer, TRefC1}, {next_event,internal,{cancel_timer,TRefC2}}]}; -timeout(_, _, _, State, Data) -> - {next_state,State,Data}. +timeout(_, _, _, Data) -> + {keep_state,Data}. timeout2( - internal, {cancel_timer,TRefC2}, timeout, _, {From,Time,TRef2}) -> + internal, {cancel_timer,TRefC2}, timeout, {From,Time,TRef2}) -> Time4 = Time * 4, receive after Time4 -> ok end, {next_state,timeout3,{From,TRef2}, [{cancel_timer,TRefC2}]}; -timeout2(_, _, _, State, Data) -> - {next_state,State,Data}. +timeout2(_, _, _, Data) -> + {keep_state,Data}. -timeout3(info, {timeout,TRef2,Result}, _, _, {From,TRef2}) -> +timeout3(info, {timeout,TRef2,Result}, _, {From,TRef2}) -> gen_statem:reply([{reply,From,Result}]), {next_state,idle,state}; -timeout3(_, _, _, State, Data) -> - {next_state,State,Data}. +timeout3(_, _, _, Data) -> + {keep_state,Data}. -wfor_conf({call,From}, confirm, _, _, Data) -> +wfor_conf({call,From}, confirm, _, Data) -> {next_state,connected,Data, [{reply,From,yes}]}; -wfor_conf(cast, {ping,_,_}, _, _, _) -> +wfor_conf(cast, {ping,_,_}, _, _) -> {keep_state_and_data,[postpone]}; -wfor_conf(cast, confirm, _, _, Data) -> +wfor_conf(cast, confirm, _, Data) -> {next_state,connected,Data}; -wfor_conf(Type, Content, PrevState, State, Data) -> - case handle_common_events(Type, Content, PrevState, State, Data) of +wfor_conf(Type, Content, _, Data) -> + case handle_common_events(Type, Content, wfor_conf, Data) of undefined -> case Type of {call,From} -> @@ -1239,113 +1239,113 @@ wfor_conf(Type, Content, PrevState, State, Data) -> Result end. -connected({call,From}, {msg,Ref}, _, State, Data) -> - {next_state,State,Data, +connected({call,From}, {msg,Ref}, _, Data) -> + {keep_state,Data, [{reply,From,{ack,Ref}}]}; -connected(cast, {msg,From,Ref}, _, State, Data) -> +connected(cast, {msg,From,Ref}, _, Data) -> From ! {ack,Ref}, - {next_state,State,Data}; -connected({call,From}, disconnect, _, _, Data) -> + {keep_state,Data}; +connected({call,From}, disconnect, _, Data) -> {next_state,idle,Data, [{reply,From,yes}]}; -connected(cast, disconnect, _, _, Data) -> +connected(cast, disconnect, _, Data) -> {next_state,idle,Data}; -connected(cast, {ping,Pid,Tag}, _, State, Data) -> +connected(cast, {ping,Pid,Tag}, _, Data) -> Pid ! {pong,Tag}, - {next_state,State,Data}; -connected(Type, Content, PrevState, State, Data) -> - case handle_common_events(Type, Content, PrevState, State, Data) of + {keep_state,Data}; +connected(Type, Content, _, Data) -> + case handle_common_events(Type, Content, connected, Data) of undefined -> case Type of {call,From} -> - {next_state,State,Data, + {keep_state,Data, [{reply,From,'eh?'}]}; _ -> - {next_state,State,Data} + {keep_state,Data} end; Result -> Result end. -state0({call,From}, stop, _, _, Data) -> +state0({call,From}, stop, _, Data) -> {stop_and_reply,normal,[{reply,From,stopped}],Data}; -state0(Type, Content, PrevState, State, Data) -> - case handle_common_events(Type, Content, PrevState, State, Data) of +state0(Type, Content, _, Data) -> + case handle_common_events(Type, Content, state0, Data) of undefined -> - {next_state,State,Data}; + {keep_state,Data}; Result -> Result end. -hiber_idle({call,From}, 'alive?', _, State, Data) -> - {next_state,State,Data, +hiber_idle({call,From}, 'alive?', _, Data) -> + {keep_state,Data, [{reply,From,'alive!'}]}; -hiber_idle({call,From}, hibernate_sync, _, _, Data) -> +hiber_idle({call,From}, hibernate_sync, _, Data) -> {next_state,hiber_wakeup,Data, [{reply,From,hibernating}, hibernate]}; -hiber_idle(info, hibernate_later, _, State, _) -> +hiber_idle(info, hibernate_later, _, _) -> Tref = erlang:start_timer(1000, self(), hibernate), - {next_state,State,Tref}; -hiber_idle(info, hibernate_now, _, State, Data) -> - {next_state,State,Data, + {keep_state,Tref}; +hiber_idle(info, hibernate_now, _, Data) -> + {keep_state,Data, [hibernate]}; -hiber_idle(info, {timeout,Tref,hibernate}, _, State, Tref) -> - {next_state,State,[], +hiber_idle(info, {timeout,Tref,hibernate}, _, Tref) -> + {keep_state,[], [hibernate]}; -hiber_idle(cast, hibernate_async, _, _, Data) -> +hiber_idle(cast, hibernate_async, _, Data) -> {next_state,hiber_wakeup,Data, [hibernate]}; -hiber_idle(Type, Content, PrevState, State, Data) -> - case handle_common_events(Type, Content, PrevState, State, Data) of +hiber_idle(Type, Content, _, Data) -> + case handle_common_events(Type, Content, hiber_idle, Data) of undefined -> - {next_state,State,Data}; + {keep_state,Data}; Result -> Result end. -hiber_wakeup({call,From}, wakeup_sync, _, _, Data) -> +hiber_wakeup({call,From}, wakeup_sync, _, Data) -> {next_state,hiber_idle,Data, [{reply,From,good_morning}]}; -hiber_wakeup({call,From}, snooze_sync, _, State, Data) -> - {next_state,State,Data, +hiber_wakeup({call,From}, snooze_sync, _, Data) -> + {keep_state,Data, [{reply,From,please_just_five_more}, hibernate]}; -hiber_wakeup(cast, wakeup_async, _, _, Data) -> +hiber_wakeup(cast, wakeup_async, _, Data) -> {next_state,hiber_idle,Data}; -hiber_wakeup(cast, snooze_async, _, State, Data) -> - {next_state,State,Data, +hiber_wakeup(cast, snooze_async, _, Data) -> + {keep_state,Data, [hibernate]}; -hiber_wakeup(Type, Content, PrevState, State, Data) -> - case handle_common_events(Type, Content, PrevState, State, Data) of +hiber_wakeup(Type, Content, _, Data) -> + case handle_common_events(Type, Content, hiber_wakeup, Data) of undefined -> - {next_state,State,Data}; + {keep_state,Data}; Result -> Result end. -handle_common_events({call,From}, get, _, State, Data) -> - {next_state,State,Data, +handle_common_events({call,From}, get, State, Data) -> + {keep_state,Data, [{reply,From,{state,State,Data}}]}; -handle_common_events(cast, {get,Pid}, _, State, Data) -> +handle_common_events(cast, {get,Pid}, State, Data) -> Pid ! {state,State,Data}, - {next_state,State,Data}; -handle_common_events({call,From}, stop, _, _, Data) -> + {keep_state,Data}; +handle_common_events({call,From}, stop, _, Data) -> {stop_and_reply,normal,[{reply,From,stopped}],Data}; -handle_common_events(cast, stop, _, _, _) -> +handle_common_events(cast, stop, _, _) -> {stop,normal}; -handle_common_events({call,From}, {stop,Reason}, _, _, Data) -> +handle_common_events({call,From}, {stop,Reason}, _, Data) -> {stop_and_reply,Reason,{reply,From,stopped},Data}; -handle_common_events(cast, {stop,Reason}, _, _, _) -> - {stop,Reason}; -handle_common_events({call,From}, 'alive?', _, State, Data) -> - {next_state,State,Data, +handle_common_events(cast, {stop,Reason}, _, _) -> + {stop,Reason}; +handle_common_events({call,From}, 'alive?', _, Data) -> + {keep_state,Data, [{reply,From,yes}]}; -handle_common_events(cast, {'alive?',Pid}, _, State, Data) -> +handle_common_events(cast, {'alive?',Pid}, _, Data) -> Pid ! yes, - {next_state,State,Data}; -handle_common_events(_, _, _, _, _) -> + {keep_state,Data}; +handle_common_events(_, _, _, _) -> undefined. %% Dispatcher to test callback_mode handle_event_function @@ -1356,8 +1356,7 @@ handle_common_events(_, _, _, _, _) -> handle_event(Type, Event, PrevState, State, Data) -> PrevStateName = unwrap_state(PrevState), StateName = unwrap_state(State), - try ?MODULE:StateName( - Type, Event, PrevStateName, StateName, Data) of + try ?MODULE:StateName(Type, Event, PrevStateName, Data) of Result -> wrap_result(Result) catch -- cgit v1.2.3