diff options
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 70 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 23 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 81 |
3 files changed, 73 insertions, 101 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 1e41d616e9..f21610d14c 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -62,12 +62,12 @@ gen_statem:stop -----> Module:terminate/2 gen_statem:call gen_statem:cast erlang:send -erlang:'!' -----> Module:StateName/4 - Module:handle_event/5 +erlang:'!' -----> Module:StateName/3 + Module:handle_event/4 - -----> Module:terminate/3 -- -----> Module:code_change/3</pre> +- -----> Module:code_change/4</pre> <p>Events are of different <seealso marker="#type-event_type">types</seealso> so the callback functions can know the origin of an event @@ -91,28 +91,22 @@ erlang:'!' -----> Module:StateName/4 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/4"> - <c>Module:StateName/4</c> + <seealso marker="#Module:StateName/3"> + <c>Module:StateName/3</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. + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> makes the state name <c>terminate</c> unusable. </p> <p>When <seealso marker="#type-callback_mode">callback_mode</seealso> is <c>handle_event_function</c> the state can be any term and the state function name is - <seealso marker="#Module:handle_event/5"> - <c>Module:handle_event/5</c> + <seealso marker="#Module:handle_event/4"> + <c>Module:handle_event/4</c> </seealso>. This makes it easy to dispatch on state or on event as you like but you will have to implement it. @@ -120,12 +114,6 @@ erlang:'!' -----> Module:StateName/4 states so you do not accidentally postpone one event forever creating an infinite busy loop. </p> - <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 <seealso marker="#state_function">state function</seealso> @@ -378,14 +366,14 @@ erlang:'!' -----> Module:StateName/4 <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/4"> - <c>Module:StateName/4</c> - </seealso> is used. This is the default. + <seealso marker="#Module:StateName/3"> + <c>Module:StateName/3</c> + </seealso> is used. </item> <tag><c>handle_event_function</c></tag> <item>The state can be any term and the callback function - <seealso marker="#Module:handle_event/5"> - <c>Module:handle_event/5</c> + <seealso marker="#Module:handle_event/4"> + <c>Module:handle_event/4</c> </seealso> is used for all states. </item> </taglist> @@ -1054,9 +1042,10 @@ erlang:'!' -----> Module:StateName/4 <c>CallbackMode</c> selects the <seealso marker="#type-callback_mode">callback_mode()</seealso>. of the <c>gen_statem</c>. - <c>State</c> is the <seealso marker="#type-state">state()</seealso> - of the <c>gen_statem</c> and - <c>Data</c> the server <seealso marker="#type-data">data()</seealso> + <c>State</c> is the initial + <seealso marker="#type-state">state()</seealso> + and <c>Data</c> the initial server + <seealso marker="#type-data">data()</seealso> </p> <p>The <seealso marker="#type-action"><c>Actions</c></seealso> are executed when entering the first @@ -1076,11 +1065,10 @@ erlang:'!' -----> Module:StateName/4 </func> <func> - <name>Module:StateName(EventType, EventContent, - PrevStateName, Data) -> Result + <name>Module:StateName(EventType, EventContent, Data) -> Result </name> <name>Module:handle_event(EventType, EventContent, - PrevState, State, Data) -> Result + State, Data) -> Result </name> <fsummary>Handle an event</fsummary> <type> @@ -1088,10 +1076,7 @@ erlang:'!' -----> Module:StateName/4 <seealso marker="#type-event_type">event_type()</seealso> </v> <v>EventContent = term()</v> - <v>PrevStateName = - <seealso marker="#type-state_name">state_name()</seealso> - </v> - <v>PrevState = State = + <v>State = NewState = <seealso marker="#type-state">state()</seealso> </v> <v>Data = NewData = @@ -1109,9 +1094,9 @@ erlang:'!' -----> Module:StateName/4 <seealso marker="#cast/2">cast/2</seealso> or 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, + is <c>state_functions</c> then <c>Module:StateName/3</c> is called, and if it is <c>handle_event_function</c> - then <c>Module:handle_event/5</c> is called. + then <c>Module:handle_event/4</c> is called. </p> <p>If <c>EventType</c> is <seealso marker="#type-event_type"><c>{call,Caller}</c></seealso> @@ -1126,13 +1111,6 @@ erlang:'!' -----> Module:StateName/4 <c>reply(Caller, Reply)</c> </seealso>. </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, 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> <p>If this function returns with a new state that does not match equal (<c>=/=</c>) to the current state all postponed events will be retried in the new state. diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 7fbc1e0f0d..c844b76653 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -184,7 +184,6 @@ -callback state_name( event_type(), EventContent :: term(), - PrevStateName :: state_name(), % Previous state name Data :: data()) -> state_callback_result(). %% @@ -195,7 +194,6 @@ -callback handle_event( event_type(), EventContent :: term(), - PrevState :: state(), % Previous state State :: state(), % Current state Data :: data()) -> state_callback_result(). @@ -234,10 +232,10 @@ [init/1, % One may use enter_loop/5,6,7 instead format_status/2, % Has got a default implementation %% - state_name/4, % Example for callback_mode =:= state_functions: + state_name/3, % 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 + handle_event/4]). % For callback_mode =:= handle_event_function %% Type validation functions callback_mode(CallbackMode) -> @@ -518,7 +516,7 @@ enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) true -> Name = gen:get_proc_name(Server), Debug = gen:debug_options(Name, Opts), - PrevState = undefined, + OldState = make_ref(), NewActions = if is_list(Actions) -> @@ -530,8 +528,7 @@ enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) callback_mode => CallbackMode, module => Module, name => Name, - prev_state => PrevState, - state => PrevState, % Discarded by loop_event_actions + state => OldState, % Discarded by loop_event_actions data => Data, timer => undefined, postponed => [], @@ -539,7 +536,7 @@ enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) loop_event_actions( Parent, Debug, S, [], {event,undefined}, % Discarded due to {postpone,false} - PrevState, State, Data, NewActions); + OldState, State, Data, NewActions); false -> erlang:error( badarg, @@ -771,7 +768,6 @@ loop_events( Parent, Debug, #{callback_mode := CallbackMode, module := Module, - prev_state := PrevState, state := State, data := Data} = S, [{Type,Content} = Event|Events] = Q, Timer) -> @@ -780,9 +776,9 @@ loop_events( try case CallbackMode of state_functions -> - Module:State(Type, Content, PrevState, Data); + Module:State(Type, Content, Data); handle_event_function -> - Module:handle_event(Type, Content, PrevState, State, Data) + Module:handle_event(Type, Content, State, Data) end of Result -> loop_event_result( @@ -796,7 +792,7 @@ loop_events( %% of calling a nonexistent state function case erlang:get_stacktrace() of [{Module,State, - [Type,Content,PrevState,Data]=Args, + [Type,Content,Data]=Args, _} |Stacktrace] when CallbackMode =:= state_functions -> @@ -806,7 +802,7 @@ loop_events( Stacktrace, Debug, S, Q); [{Module,handle_event, - [Type,Content,PrevState,State,Data]=Args, + [Type,Content,State,Data]=Args, _} |Stacktrace] when CallbackMode =:= handle_event_function -> @@ -932,7 +928,6 @@ loop_event_actions( loop_events( Parent, NewDebug, S#{ - prev_state := State, state := NewState, data := NewData, timer := Timer, diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 3302171329..27b042c0d8 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -1160,26 +1160,26 @@ 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}, _, _Data) -> +idle({call,From}, {timeout,Time}, _Data) -> {next_state,timeout,{From,Time}, [{timeout,Time,idle}]}; -idle(Type, Content, PrevState, Data) -> +idle(Type, Content, Data) -> case handle_common_events(Type, Content, idle, Data) of undefined -> case Type of @@ -1187,45 +1187,45 @@ idle(Type, Content, PrevState, Data) -> throw({keep_state,Data,[{reply,From,'eh?'}]}); _ -> throw( - {stop,{unexpected,idle,Type,Content,PrevState}}) + {stop,{unexpected,idle,Type,Content}}) end; Result -> Result end. -timeout(timeout, idle, idle, {From,Time}) -> +timeout(timeout, 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(_, _, _, Data) -> +timeout(_, _, Data) -> {keep_state,Data}. timeout2( - internal, {cancel_timer,TRefC2}, timeout, {From,Time,TRef2}) -> + internal, {cancel_timer,TRefC2}, {From,Time,TRef2}) -> Time4 = Time * 4, receive after Time4 -> ok end, {next_state,timeout3,{From,TRef2}, [{cancel_timer,TRefC2}]}; -timeout2(_, _, _, 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(_, _, _, 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, _, Data) -> +wfor_conf(Type, Content, Data) -> case handle_common_events(Type, Content, wfor_conf, Data) of undefined -> case Type of @@ -1239,21 +1239,21 @@ wfor_conf(Type, Content, _, Data) -> Result end. -connected({call,From}, {msg,Ref}, _, Data) -> +connected({call,From}, {msg,Ref}, Data) -> {keep_state,Data, [{reply,From,{ack,Ref}}]}; -connected(cast, {msg,From,Ref}, _, Data) -> +connected(cast, {msg,From,Ref}, Data) -> From ! {ack,Ref}, {keep_state,Data}; -connected({call,From}, disconnect, _, 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}, _, Data) -> +connected(cast, {ping,Pid,Tag}, Data) -> Pid ! {pong,Tag}, {keep_state,Data}; -connected(Type, Content, _, Data) -> +connected(Type, Content, Data) -> case handle_common_events(Type, Content, connected, Data) of undefined -> case Type of @@ -1267,9 +1267,9 @@ connected(Type, Content, _, Data) -> Result end. -state0({call,From}, stop, _, Data) -> +state0({call,From}, stop, Data) -> {stop_and_reply,normal,[{reply,From,stopped}],Data}; -state0(Type, Content, _, Data) -> +state0(Type, Content, Data) -> case handle_common_events(Type, Content, state0, Data) of undefined -> {keep_state,Data}; @@ -1277,26 +1277,26 @@ state0(Type, Content, _, Data) -> Result end. -hiber_idle({call,From}, 'alive?', _, 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, _, _) -> +hiber_idle(info, hibernate_later, _) -> Tref = erlang:start_timer(1000, self(), hibernate), {keep_state,Tref}; -hiber_idle(info, hibernate_now, _, Data) -> +hiber_idle(info, hibernate_now, Data) -> {keep_state,Data, [hibernate]}; -hiber_idle(info, {timeout,Tref,hibernate}, _, Tref) -> +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, _, Data) -> +hiber_idle(Type, Content, Data) -> case handle_common_events(Type, Content, hiber_idle, Data) of undefined -> {keep_state,Data}; @@ -1304,19 +1304,19 @@ hiber_idle(Type, Content, _, Data) -> 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, _, 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, _, Data) -> +hiber_wakeup(cast, snooze_async, Data) -> {keep_state,Data, [hibernate]}; -hiber_wakeup(Type, Content, _, Data) -> +hiber_wakeup(Type, Content, Data) -> case handle_common_events(Type, Content, hiber_wakeup, Data) of undefined -> {keep_state,Data}; @@ -1353,10 +1353,9 @@ handle_common_events(_, _, _, _) -> %% Wrap the state in a 1 element list just to test non-atom %% states. Note that the state from init/1 is not wrapped %% so both atom and non-atom states are tested. -handle_event(Type, Event, PrevState, State, Data) -> - PrevStateName = unwrap_state(PrevState), +handle_event(Type, Event, State, Data) -> StateName = unwrap_state(State), - try ?MODULE:StateName(Type, Event, PrevStateName, Data) of + try ?MODULE:StateName(Type, Event, Data) of Result -> wrap_result(Result) catch |