From 3815de0c7337058991066454c246587c0dbaa664 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 16 Feb 2016 10:51:39 +0100 Subject: Change to {next_state,...} and {stop,...} return format --- lib/stdlib/doc/src/gen_statem.xml | 75 +++++++++++------- lib/stdlib/src/gen_statem.erl | 103 +++++++++++++++--------- lib/stdlib/test/gen_statem_SUITE.erl | 148 ++++++++++++++++++----------------- 3 files changed, 191 insertions(+), 135 deletions(-) (limited to 'lib/stdlib') diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 83d1f1e9e6..f7ce0f61ae 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -397,23 +397,6 @@ erlang:'!' -----> Module:State/5 - - {reply,Client,Reply} - - Reply to a client that called - call/2. - Client must be the term from the - - {call,Client} - argument to the - state function. - - {stop,Reason} - The gen_statem will call - - Module:terminate/3 - with Reason and terminate. - {insert_event,EventType,EventContent} @@ -471,6 +454,24 @@ erlang:'!' -----> Module:State/5 + + + + + + {reply,Client,Reply} + + Reply to a client that called + call/2. + Client must be the term from the + + {call,Client} + argument to the + state function. + + + + @@ -867,22 +868,39 @@ erlang:'!' -----> Module:State/5 event_type() EventContent = term() - Result = {NewState,NewStateData,StateOps} -   | {NewState,NewStateData} -   The same as {NewState,NewStateData,[]} -   | {NewStateData} -   The same as {State,NewStateData,[retry]} -   | {} -   The same as {State,StateData,[]} -   | StateOps -   The same as {State,StateData,StateOps} - + Result = {stop,Reason,NewStateData} +   The same as {stop,Reason,[],NewStateData} +   | {stop,Reason,Reply,NewStateData} +   The same as + {stop,Reason,[Reply],NewStateData} + +   | {stop,Reason,Replies,NewStateData} +   The gen_statem will first send all + Replies and then call + + Module:terminate/3 + with Reason and terminate. + +   | {next_state,NewState,NewStateData} +   The same as + {next_state,NewState,NewStateData,NewStateData,[]} + +   | {next_state,NewState,NewStateData,StateOps} +   The gen_statem will do a state transition to + NewState (which may be the same as State) + and execute all StateOps + + Reason = term() PrevState = State = NewState = state() StateData = NewStateData = state_data() + Reply = + reply_operation() + + Replies = [Reply] StateOps = [state_op()] @@ -898,7 +916,8 @@ erlang:'!' -----> Module:State/5 from this or from any other state function by returning with {reply,Client,Reply} in - StateOps + StateOps, in + Replies or by calling gen_statem:reply(Client, Reply) diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 9bb5ed013b..7c3cd8c2f3 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -74,14 +74,13 @@ {'retry', Retry :: boolean()} | 'hibernate' | % Hibernate the server instead of going into receive {'hibernate', Hibernate :: boolean()} | + (Timeout :: timeout()) | % {timeout,Timeout} {'timeout', % Generate a ('timeout', Msg, ...) event after Time Time :: timeout(), Msg :: term()}. -type state_operation() :: %% These can occur multiple times and are executed in order %% of appearence in the state_op() list - {'reply', % Reply to a client - Client :: client(), Reply :: term()} | - {'stop', Reason :: term()} | % Stop the server + reply_operation() | {'insert_event', % Insert event as the next to handle EventType :: event_type(), EventContent :: term()} | @@ -95,6 +94,9 @@ MonitorRef :: reference()} | {'unlink', % Unlink and clean up mess(ages) Id :: pid() | port()}. +-type reply_operation() :: + {'reply', % Reply to a client + Client :: client(), Reply :: term()}. %% The state machine init function. It is called only once and %% the server is not running until this function has returned @@ -122,12 +124,20 @@ PrevState :: state(), State :: state(), % Current state StateData :: state_data()) -> - [state_op()] | % {State,StateData,[state_op()]} - {} | % {State,StateData,[]} - {NewStateData :: state_data()} | % {State,NewStateData,[retry]} - {NewState :: state(), - NewStateData :: state_data()} | % {NewState,NewStateData,[]} - {NewState :: state(), NewStateData :: state_data(), [state_op()]}. + {stop, % Stop the server + Reason :: term(), + NewStateData :: state_data()} | + {stop, % Stop the server + Reason :: term(), + [reply_operation()] | reply_operation(), + NewStateData :: state_data()} | + {next_state, % {next_state,NewState,NewStateData,[]} + NewState :: state(), + NewStateData :: state_data()} | + {next_state, % State transition, maybe to the same state + NewState :: state(), + NewStateData :: state_data(), + [state_op()] | state_op()}. %% Clean up before the server terminates. -callback terminate( @@ -679,35 +689,42 @@ loop_events( terminate(Class, Reason, Stacktrace, Debug, S, Q) end. -%% Interprete all callback return value variants -loop_event_result( - Parent, Debug, - #{state := State, state_data := StateData} = S, - Events, Event, Result) -> +%% Interpret all callback return value variants +loop_event_result(Parent, Debug, S, Events, Event, Result) -> case Result of - {} -> % Ignore - loop_event_state_ops( - Parent, Debug, S, Events, Event, - State, StateData, []); - {NewStateData} -> % Retry - loop_event_state_ops( - Parent, Debug, S, Events, Event, - State, NewStateData, [retry]); - {NewState,NewStateData} -> % Consume + {stop,Reason,NewStateData} -> + terminate( + Reason, Debug, + S#{state_data := NewStateData}, + [Event|Events]); + {stop,Reason,Reply,NewStateData} -> + NewS = S#{state_data := NewStateData}, + Q = [Event|Events], + Replies = + if + is_list(Reply) -> + Reply; + true -> + [Reply] + end, + BadReplies = + reply_then_terminate(Reason, Debug, NewS, Q, Replies), + %% Since it returned Replies was bad + terminate( + {bad_return_value,{stop,Reason,BadReplies,NewStateData}}, + Debug, NewS, Q); + {next_state,NewState,NewStateData} -> loop_event_state_ops( Parent, Debug, S, Events, Event, NewState, NewStateData, []); - {NewState,NewStateData,StateOps} when is_list(StateOps) -> + {next_state,NewState,NewStateData,StateOps} + when is_list(StateOps) -> loop_event_state_ops( Parent, Debug, S, Events, Event, NewState, NewStateData, StateOps); - StateOps when is_list(StateOps) -> % Stay in state - loop_event_state_ops( - Parent, Debug, S, Events, Event, - State, StateData, StateOps); - BadReturn -> + _ -> terminate( - {bad_return_value,BadReturn}, Debug, S, [Event|Events]) + {bad_return_value,Result}, Debug, S, [Event|Events]) end. loop_event_state_ops( @@ -750,7 +767,8 @@ loop_event_state_ops( %% Pretend the timeout has just been received {undefined,Q3 ++ [{timeout,Msg}]}; {timeout,Time,Msg} -> - {erlang:start_timer(Time, self(), Msg),Q3} + {erlang:start_timer(Time, self(), Msg), + Q3} end, loop_events( Parent, NewDebug, @@ -820,11 +838,8 @@ process_state_operations([], Debug, _S, Q, P) -> process_state_operations([Operation|Operations] = OOs, Debug, S, Q, P) -> case Operation of {reply,{_To,_Tag}=Client,Reply} -> - reply(Client, Reply), - NewDebug = sys_debug(Debug, S, {out,Reply,Client}), + NewDebug = do_reply(Debug, S, Client, Reply), process_state_operations(Operations, NewDebug, S, Q, P); - {stop,Reason} -> - [Reason,Debug]; {insert_event,Type,Content} -> case event_type(Type) of true -> @@ -854,6 +869,22 @@ process_state_operations([Operation|Operations] = OOs, Debug, S, Q, P) -> end end. +reply_then_terminate(Reason, Debug, S, Q, []) -> + terminate(Reason, Debug, S, Q); +reply_then_terminate(Reason, Debug, S, Q, [R|Rs] = RRs) -> + case R of + {reply,{_To,_Tag}=Client,Reply} -> + NewDebug = do_reply(Debug, S, Client, Reply), + reply_then_terminate(Reason, NewDebug, S, Q, Rs); + _ -> + RRs % bad_return_value + end. + +do_reply(Debug, S, Client, Reply) -> + reply(Client, Reply), + sys_debug(Debug, S, {out,Reply,Client}). + + %% Remove oldest matching event from the queue(s) remove_event(RemoveFun, Q, P) -> try @@ -928,7 +959,7 @@ remove_fun({unlink,Id}) -> end catch Class:Reason -> - {Class,Reason,erlang:get_stacktrace()} + [Class,Reason,erlang:get_stacktrace()] end; remove_fun(_) -> undefined. diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 342be32acb..51e08f5ec1 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -1068,29 +1068,30 @@ terminate(_Reason, _State, _Data) -> idle(cast, {connect,Pid}, _, _, Data) -> Pid ! accept, - {wfor_conf,Data}; + {next_state,wfor_conf,Data}; idle({call,From}, connect, _, _, Data) -> - {wfor_conf,Data,[{reply,From,accept}]}; + {next_state,wfor_conf,Data,[{reply,From,accept}]}; idle(cast, badreturn, _, _, _Data) -> badreturn; idle({call,_From}, badreturn, _, _, _Data) -> badreturn; -idle({call,From}, {delayed_answer,T}, _, _, _Data) -> +idle({call,From}, {delayed_answer,T}, _, State, Data) -> receive after T -> - [{reply,From,delayed}] + {next_state,State,Data, + [{reply,From,delayed}]} end; idle({call,From}, {timeout,Time}, _, State, _Data) -> - {timeout, {From,Time}, [{timeout,Time,State}]}; + {next_state,timeout,{From,Time}, + [{timeout,Time,State}]}; idle(Type, Content, PrevState, State, Data) -> case handle_common_events(Type, Content, PrevState, State, Data) of - [] -> + undefined -> case Type of {call,From} -> - {State,Data,[{reply,From,'eh?'}]}; + {next_state,State,Data,[{reply,From,'eh?'}]}; _ -> - {State,Data, - [{stop,{unexpected,State,PrevState,Type,Content}}]} + {stop,{unexpected,State,PrevState,Type,Content},Data} end; Result -> Result @@ -1100,146 +1101,151 @@ timeout(timeout, idle, idle, timeout, {From,Time}) -> TRef2 = erlang:start_timer(Time, self(), ok), TRefC1 = erlang:start_timer(Time, self(), cancel1), TRefC2 = erlang:start_timer(Time, self(), cancel2), - {timeout2, - {From, Time, TRef2}, + {next_state,timeout2,{From,Time,TRef2}, [{cancel_timer, TRefC1}, {insert_event,internal,{cancel_timer,TRefC2}}]}; -timeout(_, _, _, _, Data) -> - {Data}. +timeout(_, _, _, State, Data) -> + {next_state,State,Data}. timeout2( internal, {cancel_timer,TRefC2}, timeout, _, {From,Time,TRef2}) -> Time4 = Time * 4, receive after Time4 -> ok end, - {timeout3,{From,TRef2},[{cancel_timer,TRefC2}]}; -timeout2(_, _, _, _, Data) -> - {Data}. + {next_state,timeout3,{From,TRef2}, + [{cancel_timer,TRefC2}]}; +timeout2(_, _, _, State, Data) -> + {next_state,State,Data}. timeout3(info, {timeout,TRef2,Result}, _, _, {From,TRef2}) -> - {idle,state,[{reply,From,Result}]}; -timeout3(_, _, _, _, Data) -> - {Data}. + {next_state,idle,state, + [{reply,From,Result}]}; +timeout3(_, _, _, State, Data) -> + {next_state,State,Data}. wfor_conf({call,From}, confirm, _, _, Data) -> - {connected,Data,[{reply,From,yes}]}; + {next_state,connected,Data, + [{reply,From,yes}]}; wfor_conf(cast, confirm, _, _, Data) -> - {connected,Data}; + {next_state,connected,Data}; wfor_conf(Type, Content, PrevState, State, Data) -> case handle_common_events(Type, Content, PrevState, State, Data) of - [] -> + undefined -> case Type of {call,From} -> - {idle,Data,[{reply,From,'eh?'}]}; + {next_state,idle,Data, + [{reply,From,'eh?'}]}; _ -> - {Data} + {next_state,State,Data} end; Result -> Result end. connected({call,From}, {msg,Ref}, _, State, Data) -> - {State,Data,[{reply,From,{ack,Ref}}]}; -connected(cast, {msg,From,Ref}, _, _, _Data) -> + {next_state,State,Data, + [{reply,From,{ack,Ref}}]}; +connected(cast, {msg,From,Ref}, _, State, Data) -> From ! {ack,Ref}, - {}; + {next_state,State,Data}; connected({call,From}, disconnect, _, _, Data) -> - {idle,Data,[{reply,From,yes}]}; + {next_state,idle,Data, + [{reply,From,yes}]}; connected(cast, disconnect, _, _, Data) -> - {idle,Data}; + {next_state,idle,Data}; connected(Type, Content, PrevState, State, Data) -> case handle_common_events(Type, Content, PrevState, State, Data) of - [] -> + undefined -> case Type of {call,From} -> - [{reply,From,'eh?'}]; + {next_state,State,Data, + [{reply,From,'eh?'}]}; _ -> - {Data} + {next_state,State,Data} end; Result -> Result end. -state0({call,From}, stop, _, State, Data) -> - {State,Data, - [{reply,From,stopped}, - {stop,normal}]}; +state0({call,From}, stop, _, _, Data) -> + {stop,normal,[{reply,From,stopped}],Data}; state0(Type, Content, PrevState, State, Data) -> case handle_common_events(Type, Content, PrevState, State, Data) of - [] -> - {Data}; + undefined -> + {next_state,State,Data}; Result -> Result end. -hiber_idle({call,From}, 'alive?', _, _, _) -> - [{reply,From,'alive!'}]; +hiber_idle({call,From}, 'alive?', _, State, Data) -> + {next_state,State,Data, + [{reply,From,'alive!'}]}; hiber_idle({call,From}, hibernate_sync, _, _, Data) -> - {hiber_wakeup,Data, + {next_state,hiber_wakeup,Data, [{reply,From,hibernating}, hibernate]}; hiber_idle(info, hibernate_later, _, State, _) -> Tref = erlang:start_timer(1000, self(), hibernate), - {State,Tref}; + {next_state,State,Tref}; hiber_idle(info, hibernate_now, _, State, Data) -> - {State,Data,[hibernate]}; + {next_state,State,Data, + [hibernate]}; hiber_idle(info, {timeout,Tref,hibernate}, _, State, Tref) -> - {State,[], + {next_state,State,[], [hibernate]}; hiber_idle(cast, hibernate_async, _, _, Data) -> - {hiber_wakeup,Data, + {next_state,hiber_wakeup,Data, [hibernate]}; hiber_idle(Type, Content, PrevState, State, Data) -> case handle_common_events(Type, Content, PrevState, State, Data) of - [] -> - {Data}; + undefined -> + {next_state,State,Data}; Result -> Result end. hiber_wakeup({call,From}, wakeup_sync, _, _, Data) -> - {hiber_idle,Data,[{reply,From,good_morning}]}; + {next_state,hiber_idle,Data, + [{reply,From,good_morning}]}; hiber_wakeup({call,From}, snooze_sync, _, State, Data) -> - {State,Data, + {next_state,State,Data, [{reply,From,please_just_five_more}, hibernate]}; hiber_wakeup(cast, wakeup_async, _, _, Data) -> - {hiber_idle,Data}; -hiber_wakeup(cast, snooze_async, _, _, _Data) -> - [hibernate]; + {next_state,hiber_idle,Data}; +hiber_wakeup(cast, snooze_async, _, State, Data) -> + {next_state,State,Data, + [hibernate]}; hiber_wakeup(Type, Content, PrevState, State, Data) -> case handle_common_events(Type, Content, PrevState, State, Data) of - [] -> - {Data}; + undefined -> + {next_state,State,Data}; Result -> Result end. handle_common_events({call,From}, get, _, State, Data) -> - [{reply,From,{state,State,Data}}]; + {next_state,State,Data, + [{reply,From,{state,State,Data}}]}; handle_common_events(cast, {get,Pid}, _, State, Data) -> Pid ! {state,State,Data}, - {}; -handle_common_events({call,From}, stop, _, _, _) -> - [{reply,From,stopped}, - {stop,normal}]; -handle_common_events(cast, stop, _, State, Data) -> - {State,Data, - [{stop,normal}]}; -handle_common_events({call,From}, {stop,Reason}, _, State, Data) -> - {State,Data, - [{reply,From,stopped}, - {stop,Reason}]}; -handle_common_events(cast, {stop,Reason}, _, _, _) -> - [{stop,Reason}]; + {next_state,State,Data}; +handle_common_events({call,From}, stop, _, _, Data) -> + {stop,normal,[{reply,From,stopped}],Data}; +handle_common_events(cast, stop, _, _, Data) -> + {stop,normal,Data}; +handle_common_events({call,From}, {stop,Reason}, _, _, Data) -> + {stop,Reason,{reply,From,stopped},Data}; +handle_common_events(cast, {stop,Reason}, _, _, Data) -> + {stop,Reason,Data}; handle_common_events({call,From}, 'alive?', _, State, Data) -> - {State,Data, + {next_state,State,Data, [{reply,From,yes}]}; handle_common_events(cast, {'alive?',Pid}, _, State, Data) -> Pid ! yes, - {State,Data}; + {next_state,State,Data}; handle_common_events(_, _, _, _, _) -> - []. + undefined. code_change(_OldVsn, State, StateData, _Extra) -> {ok,State,StateData}. -- cgit v1.2.3