diff options
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 75 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 103 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 148 |
3 files changed, 191 insertions, 135 deletions
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 @@ -398,23 +398,6 @@ erlang:'!' -----> Module:State/5 <desc> <taglist> <tag> - <c>{reply,<anno>Client</anno>,<anno>Reply</anno>}</c> - </tag> - <item>Reply to a client that called - <seealso marker="#call/2"><c>call/2</c></seealso>. - <c><anno>Client</anno></c> must be the term from the - <seealso marker="#type-event_type"> - <c>{call,<anno>Client</anno>}</c> - </seealso> argument to the - <seealso marker="#state_function">state function</seealso>. - </item> - <tag><c>{stop,<anno>Reason</anno>}</c></tag> - <item>The gen_statem will call - <seealso marker="#Module:terminate/3"> - <c>Module:terminate/3</c> - </seealso> with <c><anno>Reason</anno></c> and terminate. - </item> - <tag> <c> {insert_event,<anno>EventType</anno>,<anno>EventContent</anno>} </c> @@ -471,6 +454,24 @@ erlang:'!' -----> Module:State/5 </taglist> </desc> </datatype> + <datatype> + <name name="reply_operation" /> + <desc> + <taglist> + <tag> + <c>{reply,<anno>Client</anno>,<anno>Reply</anno>}</c> + </tag> + <item>Reply to a client that called + <seealso marker="#call/2"><c>call/2</c></seealso>. + <c><anno>Client</anno></c> must be the term from the + <seealso marker="#type-event_type"> + <c>{call,<anno>Client</anno>}</c> + </seealso> argument to the + <seealso marker="#state_function">state function</seealso>. + </item> + </taglist> + </desc> + </datatype> </datatypes> <funcs> @@ -867,22 +868,39 @@ erlang:'!' -----> Module:State/5 <seealso marker="#type-event_type">event_type()</seealso> </v> <v>EventContent = term()</v> - <v>Result = {NewState,NewStateData,StateOps}</v> - <v> | {NewState,NewStateData}</v> - <d> The same as <c>{NewState,NewStateData,[]}</c></d> - <v> | {NewStateData}</v> - <d> The same as <c>{State,NewStateData,[retry]}</c></d> - <v> | {}</v> - <d> The same as <c>{State,StateData,[]}</c></d> - <v> | StateOps</v> - <d> The same as <c>{State,StateData,StateOps}</c></d> - + <v>Result = {stop,Reason,NewStateData}</v> + <d> The same as <c>{stop,Reason,[],NewStateData}</c></d> + <v> | {stop,Reason,Reply,NewStateData}</v> + <d> The same as + <c>{stop,Reason,[Reply],NewStateData}</c> + </d> + <v> | {stop,Reason,Replies,NewStateData}</v> + <d> The gen_statem will first send all + <c>Replies</c> and then call + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> with <c>Reason</c> and terminate. + </d> + <v> | {next_state,NewState,NewStateData}</v> + <d> The same as + <c>{next_state,NewState,NewStateData,NewStateData,[]}</c> + </d> + <v> | {next_state,NewState,NewStateData,StateOps}</v> + <d> The gen_statem will do a state transition to + <c>NewState</c> (which may be the same as <c>State</c>) + and execute all <c>StateOps</c> + </d> + <v>Reason = term()</v> <v>PrevState = State = NewState = <seealso marker="#type-state">state()</seealso> </v> <v>StateData = NewStateData = <seealso marker="#type-state_data">state_data()</seealso> </v> + <v>Reply = + <seealso marker="#type-reply_operation">reply_operation()</seealso> + </v> + <v>Replies = [Reply]</v> <v>StateOps = [<seealso marker="#type-state_op">state_op()</seealso>] </v> @@ -898,7 +916,8 @@ erlang:'!' -----> Module:State/5 from this or from any other <seealso marker="#state_function">state function</seealso> by returning with <c>{reply,Client,Reply}</c> in - <seealso marker="#type-state_op">StateOps</seealso> + <seealso marker="#type-state_op">StateOps</seealso>, in + <seealso marker="#type-reply_operation">Replies</seealso> or by calling <seealso marker="#reply/2"> <c>gen_statem:reply(Client, Reply)</c> 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}. |