diff options
Diffstat (limited to 'lib/stdlib')
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 64 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 36 | ||||
-rw-r--r-- | 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 <c>gen_statem</c> is the callback function that is called for all events in this state, and is selected depending on <seealso marker="#type-callback_mode">callback_mode</seealso> - that the implementation selects during <c>gen_statem</c> init. + that the implementation specifies during <c>gen_statem</c> init. </p> <p>When <seealso marker="#type-callback_mode">callback_mode</seealso> is <c>state_functions</c> the state has to be an atom and is used as the state function name. See - <seealso marker="#Module:StateName/5"> - <c>Module:StateName/5</c> + <seealso marker="#Module:StateName/4"> + <c>Module:StateName/4</c> </seealso>. 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 + <seealso marker="#Module:code_change/4"> + <c>Module:code_change/4</c> + </seealso> makes the state name <c>code_change</c> + 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 <c>code_change</c> as a state name. </p> <p>When <seealso marker="#type-callback_mode">callback_mode</seealso> @@ -110,13 +120,11 @@ erlang:'!' -----> Module:StateName/5 states so you do not accidentally postpone one event forever creating an infinite busy loop. </p> - <p>Any state name or any state value (depending on - <seealso marker="#type-callback_mode">callback_mode</seealso>) - is permitted with a small gotcha regarding the state - <c>undefined</c> that is used as the previous state when - the first <c>gen_statem</c> state function is called. - You might need to know about this faked state if you - inspect the previous state argument in your state functions. + <p>There is a small gotcha in both callback modes regarding + the state <c>undefined</c> that is used as the previous state + when the first state function is called. So either do not + use <c>undefined</c> as a state, or figure out + the implications yourself. </p> <p>The <c>gen_statem</c> 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. </p> </description> @@ -350,13 +358,20 @@ erlang:'!' -----> Module:StateName/5 <datatype> <name name="callback_mode" /> <desc> + <p> + The <c>callback_mode</c> is selected when starting the + <c>gen_statem</c> using the return value from + <seealso marker="#Module:init/1">Module:init/1</seealso> + or when calling + <seealso marker="#enter_loop/5">enter_loop/5-7</seealso>. + </p> <taglist> <tag><c>state_functions</c></tag> <item>The state has to be of type <seealso marker="#type-state_name"><c>state_name()</c></seealso> and one callback function per state that is - <seealso marker="#Module:StateName/5"> - <c>Module:StateName/5</c> + <seealso marker="#Module:StateName/4"> + <c>Module:StateName/4</c> </seealso> is used. This is the default. </item> <tag><c>handle_event_function</c></tag> @@ -384,7 +399,7 @@ erlang:'!' -----> Module:StateName/5 <seealso marker="#state_function">state function</seealso>, from <seealso marker="#Module:init/1">Module:init/1</seealso> or given to - <seealso marker="#enter_loop/5">enter_loop/5,6</seealso>. + <seealso marker="#enter_loop/6">enter_loop/6,7</seealso>. </p> <p>The processing order for a state change is:</p> <list type="ordered"> @@ -990,7 +1005,7 @@ erlang:'!' -----> Module:StateName/5 <func> <name>Module:StateName(EventType, EventContent, - PrevStateName, StateName, Data) -> Result + PrevStateName, Data) -> Result </name> <name>Module:handle_event(EventType, EventContent, PrevState, State, Data) -> Result @@ -1004,9 +1019,6 @@ erlang:'!' -----> Module:StateName/5 <v>PrevStateName = <seealso marker="#type-state_name">state_name()</seealso> </v> - <v>StateName = - <seealso marker="#type-state_name">state_name()</seealso> - </v> <v>PrevState = State = <seealso marker="#type-state">state()</seealso> </v> @@ -1023,10 +1035,9 @@ erlang:'!' -----> Module:StateName/5 <p>Whenever a <c>gen_statem</c> receives an event from <seealso marker="#call/2">call/2</seealso>, <seealso marker="#cast/2">cast/2</seealso> or - as a normal process message this function is called. - If - <seealso marker="#type-callback_mode">callback_mode</seealso> - is <c>state_functions</c> then <c>Module:StateName/5</c> is called, + as a normal process message one of these functions is called. + If <seealso marker="#type-callback_mode">callback_mode</seealso> + is <c>state_functions</c> then <c>Module:StateName/4</c> is called, and if it is <c>handle_event_function</c> then <c>Module:handle_event/5</c> is called. </p> @@ -1043,13 +1054,10 @@ erlang:'!' -----> Module:StateName/5 <c>reply(Caller, Reply)</c> </seealso>. </p> - <p><c>StateName</c> 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 <c>StateName</c>. - </p> <p><c>PrevStateName</c> and <c>PrevState</c> 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 <c>gen_statem</c> enters its first state this is set to <c>undefined</c>. </p> 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 |