diff options
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 80 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 131 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 53 |
3 files changed, 131 insertions, 133 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index 8462f5ff5f..bd210a0d22 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -190,7 +190,7 @@ erlang:'!' -----> Module:StateName/5 <name name="server_name" /> <desc> <p>Name specification to use when starting - a<c>gen_statem</c> server. + a <c>gen_statem</c> server. See <seealso marker="#start_link/3"> <c>start_link/3</c> </seealso> and @@ -348,17 +348,6 @@ erlang:'!' -----> Module:StateName/5 </desc> </datatype> <datatype> - <name name="init_option" /> - <desc> - <p>Option that only is valid when initializing - the <c>gen_statem</c> that is it can be returned from - <seealso marker="#Module:init/1">Module:init/1</seealso> - or given to - <seealso marker="#enter_loop/5">enter_loop/5,6</seealso>. - </p> - </desc> - </datatype> - <datatype> <name name="callback_mode" /> <desc> <taglist> @@ -459,7 +448,7 @@ erlang:'!' -----> Module:StateName/5 <seealso marker="#type-event_type">type <c>timeout</c></seealso> after <c><anno>Time</anno></c> milliseconds unless some other event is received before that time. Note that a retried - event counts just like a new in this respect. + or inserted event counts just like a new in this respect. If <c>Time =:= infinity</c> no timer is started. If <c>Time =:= 0</c> the timeout event is immediately enqueued as the newest received. @@ -468,7 +457,7 @@ erlang:'!' -----> Module:StateName/5 <seealso marker="#type-transition_action"> <c>transition_action()</c> </seealso> <c>cancel_timer</c>. - This timeout is cancelled automatically by any event. + This timeout is cancelled automatically by any other event. </item> </taglist> </desc> @@ -478,9 +467,9 @@ erlang:'!' -----> Module:StateName/5 <desc> <p>The state transition actions are executed in the containing list order. This matters - for <c>next_event</c> where the last one in the list - will become the next event to present - to the state functions. Regarding the other actions + for <c>next_event</c> where the last such in the list + will become the next event to process by + the current state function. Regarding the other actions it is only for <c>remove_event</c> with <c><anno>EventPredicate</anno></c> and for <c>reply_action()</c> that the order may matter. @@ -490,7 +479,9 @@ erlang:'!' -----> Module:StateName/5 <item>Reply to a calling client.</item> <tag><c>next_event</c></tag> <item>Insert the given <c><anno>EventType</anno></c> - and <c><anno>EventContent</anno></c> the next to process. + and <c><anno>EventContent</anno></c> as the next to process. + This will bypass any events in the process mailbox as well + as any other queued events. An event of type <seealso marker="#type-event_type"> <c>internal</c> @@ -841,11 +832,11 @@ erlang:'!' -----> Module:StateName/5 </func> <func> - <name name="enter_loop" arity="4" /> + <name name="enter_loop" arity="5" /> <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> <desc> <p>The same as - <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> except that no <seealso marker="#type-server_name"> <c>server_name()</c> @@ -854,12 +845,12 @@ erlang:'!' -----> Module:StateName/5 </desc> </func> <func> - <name name="enter_loop" arity="5" /> + <name name="enter_loop" arity="6" /> <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> <desc> <p>If <c><anno>Server_or_Ops</anno></c> is a <c>list()</c> the same as - <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> except that no <seealso marker="#type-server_name"> <c>server_name()</c> @@ -867,7 +858,7 @@ erlang:'!' -----> Module:StateName/5 <c>Ops = <anno>Server_or_Ops</anno></c>. </p> <p>Otherwise the same as - <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> with <c>Server = <anno>Server_or_Ops</anno></c> and <c>Ops = []</c>. @@ -875,7 +866,7 @@ erlang:'!' -----> Module:StateName/5 </desc> </func> <func> - <name name="enter_loop" arity="6" /> + <name name="enter_loop" arity="7" /> <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> <desc> <p>Makes an the calling process become a <c>gen_statem</c>. @@ -903,8 +894,8 @@ erlang:'!' -----> Module:StateName/5 <c>server_name()</c> </seealso> name must have been registered accordingly <em>before</em> this function is called.</p> - <p><c><anno>State</anno></c>, <c><anno>Data</anno></c> - and <c><anno>Ops</anno></c> + <p><c><anno>CallbackMode</anno></c>, <c><anno>State</anno></c>, + <c><anno>Data</anno></c> and <c><anno>Ops</anno></c> have the same meanings as in the return value of <seealso marker="#Module:init/1">Module:init/1</seealso>. Also, the callback module <c><anno>Module</anno></c> @@ -938,15 +929,17 @@ erlang:'!' -----> Module:StateName/5 <fsummary>Initialize process and internal state</fsummary> <type> <v>Args = term()</v> - <v>Result = {ok,State,Data}</v> - <v> | {ok,State,Data,Ops}</v> + <v>Result = {CallbackMode,State,Data}</v> + <v> | {CallbackMode,State,Data,Ops}</v> <v> | {stop,Reason} | ignore</v> + <v>CallbackMode = + <seealso marker="#type-callback_mode">callback_mode()</seealso> + </v> <v>State = <seealso marker="#type-state">state()</seealso></v> <v>Data = <seealso marker="#type-data">data()</seealso> </v> <v>Ops = - [<seealso marker="#type-transition_op">transition_op()</seealso> - | <seealso marker="#type-init_option">init_option()</seealso>] + [<seealso marker="#type-transition_op">transition_op()</seealso>] </v> <v>Reason = term()</v> </type> @@ -962,21 +955,20 @@ erlang:'!' -----> Module:StateName/5 <p><c>Args</c> is the <c>Args</c> argument provided to the start function.</p> <p>If the initialization is successful, the function should - return <c>{ok,State,Data}</c> or - <c>{ok,State,Data,Ops}</c>. - <c>State</c> is the <seealso marker="#type-state">state</seealso> + return <c>{CallbackMode,State,Data}</c> or + <c>{CallbackMode,State,Data,Ops}</c>. + <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> </p> <p>The <seealso marker="#type-transition_op"><c>Ops</c></seealso> are executed when entering the first <seealso marker="#type-state">state</seealso> just as for a <seealso marker="#state_function">state function</seealso>. </p> - <p>This function allows an option to select the callback mode - of the <c>gen_statem</c>. See - <seealso marker="#type-init_option">init_option</seealso>. - This option is not allowed from the state function(s). - </p> <p>If something goes wrong during the initialization the function should return <c>{stop,Reason}</c> or <c>ignore</c>. See @@ -984,7 +976,7 @@ erlang:'!' -----> Module:StateName/5 </p> <p>This function may use <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, - to return its value. + to return <c>Result</c>. </p> </desc> </func> @@ -1065,7 +1057,7 @@ erlang:'!' -----> Module:StateName/5 </p> <p>These functions may use <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, - to return its value. + to return <c>Result</c>. </p> </desc> </func> @@ -1130,7 +1122,7 @@ erlang:'!' -----> Module:StateName/5 </p> <p>This function may use <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, - to return its value. + to return <c>Ignored</c>, which is ignored anyway. </p> </desc> </func> @@ -1184,11 +1176,11 @@ erlang:'!' -----> Module:StateName/5 </p> <p>If the function returns <c>Reason</c>, the ongoing upgrade will fail and roll back to the old release.</p> - </desc> <p>This function may use <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, - to return its value. + to return <c>Result</c> or <c>Reason</c>. </p> + </desc> </func> <func> @@ -1277,7 +1269,7 @@ erlang:'!' -----> Module:StateName/5 </p> <p>This function may use <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, - to return its value. + to return <c>Status</c>. </p> </desc> </func> diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index b580eaab97..32799f5afc 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -24,7 +24,7 @@ [start/3,start/4,start_link/3,start_link/4, stop/1,stop/3, cast/2,call/2,call/3, - enter_loop/4,enter_loop/5,enter_loop/6, + enter_loop/5,enter_loop/6,enter_loop/7, reply/1,reply/2]). %% gen callbacks @@ -63,8 +63,6 @@ 'info' | 'timeout' | 'internal'. -type event_predicate() :: % Return true for the event in question fun((event_type(), term()) -> boolean()). --type init_option() :: - {'callback_mode', callback_mode()}. -type callback_mode() :: 'state_functions' | 'handle_event_function'. -type transition_op() :: %% First NewState and NewData are set, @@ -136,12 +134,12 @@ %% an {ok, ...} tuple. Thereafter the state callbacks are called %% for all events to this server. -callback init(Args :: term()) -> - {'ok', state(), data()} | - {'ok', state(), data(), [transition_op()|init_option()]} | + {callback_mode(), state(), data()} | + {callback_mode(), state(), data(), [transition_op()]} | 'ignore' | {'stop', Reason :: term()}. -%% Example callback for callback_mode =:= state_functions +%% Example state callback for callback_mode() =:= state_functions %% state name 'state_name'. %% %% In this mode all states has to be type state_name() i.e atom(). @@ -156,7 +154,7 @@ Data :: data()) -> state_callback_result(). %% -%% Callback for callback_mode =:= handle_event_function. +%% State callback for callback_mode() =:= handle_event_function. %% %% Note that state callbacks and only state callbacks have arity 5 %% and that is intended. @@ -199,7 +197,8 @@ StatusOption :: 'normal' | 'terminate'. -optional_callbacks( - [format_status/2, % Has got a default implementation + [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: %% there has to be a StateName/5 callback function for every StateName. @@ -207,6 +206,16 @@ handle_event/5]). % For callback_mode =:= handle_event_function %% Type validation functions +callback_mode(CallbackMode) -> + case CallbackMode of + state_functions -> + true; + handle_event_function -> + true; + _ -> + false + end. +%% client({Pid,Tag}) when is_pid(Pid), is_reference(Tag) -> true; client(_) -> @@ -413,38 +422,41 @@ reply({To,Tag}, Reply) -> %% the same arguments as you would have returned from init/1 -spec enter_loop( Module :: module(), Opts :: [debug_opt()], + CallbackMode :: callback_mode(), State :: state(), Data :: data()) -> no_return(). -enter_loop(Module, Opts, State, Data) -> - enter_loop(Module, Opts, State, Data, self()). +enter_loop(Module, Opts, CallbackMode, State, Data) -> + enter_loop(Module, Opts, CallbackMode, State, Data, self()). %% -spec enter_loop( Module :: module(), Opts :: [debug_opt()], + CallbackMode :: callback_mode(), State :: state(), Data :: data(), Server_or_Ops :: - server_name() | pid() | [transition_op()|init_option()]) -> + server_name() | pid() | [transition_op()]) -> no_return(). -enter_loop(Module, Opts, State, Data, Server_or_Ops) -> +enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Ops) -> if is_list(Server_or_Ops) -> enter_loop( - Module, Opts, State, Data, + Module, Opts, CallbackMode, State, Data, self(), Server_or_Ops); true -> enter_loop( - Module, Opts, State, Data, + Module, Opts, CallbackMode, State, Data, Server_or_Ops, []) end. %% -spec enter_loop( Module :: module(), Opts :: [debug_opt()], + CallbackMode :: callback_mode(), State :: state(), Data :: data(), Server :: server_name() | pid(), - Ops :: [transition_op()|init_option()]) -> + Ops :: [transition_op()]) -> no_return(). -enter_loop(Module, Opts, State, Data, Server, Ops) -> +enter_loop(Module, Opts, CallbackMode, State, Data, Server, Ops) -> Parent = gen:get_parent(), - enter(Module, Opts, State, Data, Server, Ops, Parent). + enter(Module, Opts, CallbackMode, State, Data, Server, Ops, Parent). %%--------------------------------------------------------------------------- %% API helpers @@ -465,37 +477,40 @@ do_send(Proc, Msg) -> ok end. -%% Here init_it and all enter_loop functions converge -enter(Module, Opts, State, Data, Server, InitOps, Parent) -> - Name = gen:get_proc_name(Server), - Debug = gen:debug_options(Name, Opts), - PrevState = undefined, - S = #{ - callback_mode => state_functions, - module => Module, - name => Name, - prev_state => PrevState, - state => PrevState, % Will be discarded by loop_event_transition_ops - data => Data, - timer => undefined, - postponed => [], - hibernate => false}, - case collect_init_options(InitOps) of - {CallbackMode,Ops} -> +%% Here init_it/6 and enter_loop/5,6,7 functions converge +enter(Module, Opts, CallbackMode, State, Data, Server, Ops, Parent) + when is_atom(Module), is_pid(Parent) -> + case callback_mode(CallbackMode) of + true -> + Name = gen:get_proc_name(Server), + Debug = gen:debug_options(Name, Opts), + PrevState = undefined, + S = #{ + callback_mode => CallbackMode, + module => Module, + name => Name, + prev_state => PrevState, + state => PrevState, % Discarded by loop_event_transition_ops + data => Data, + timer => undefined, + postponed => [], + hibernate => false}, loop_event_transition_ops( - Parent, Debug, - S#{callback_mode := CallbackMode}, - [], - {event,undefined}, % Will be discarded by {postpone,false} + Parent, Debug, S, [], + {event,undefined}, % Discarded due to {postpone,false} PrevState, State, Data, Ops++[{postpone,false}]); - [Reason] -> - ?TERMINATE(Reason, Debug, S, []) + false -> + erlang:error( + badarg, + [Module,Opts,CallbackMode,State,Data,Server,Ops,Parent]) end. %%%========================================================================== %%% gen callbacks +init_it(Starter, self, ServerRef, Module, Args, Opts) -> + init_it(Starter, self(), ServerRef, Module, Args, Opts); init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> try Module:init(Args) of Result -> @@ -514,12 +529,16 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> case Result of - {ok,State,Data} -> + {CallbackMode,State,Data} -> proc_lib:init_ack(Starter, {ok,self()}), - enter(Module, Opts, State, Data, ServerRef, [], Parent); - {ok,State,Data,Ops} -> + enter( + Module, Opts, CallbackMode, State, Data, + ServerRef, [], Parent); + {CallbackMode,State,Data,Ops} -> proc_lib:init_ack(Starter, {ok,self()}), - enter(Module, Opts, State, Data, ServerRef, Ops, Parent); + enter( + Module, Opts, CallbackMode, State, Data, + ServerRef, Ops, Parent); {stop,Reason} -> gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), @@ -881,30 +900,6 @@ loop_event_transition_ops( %%--------------------------------------------------------------------------- %% Server helpers -collect_init_options(InitOps) -> - if - is_list(InitOps) -> - collect_init_options(InitOps, state_functions, []); - true -> - collect_init_options([InitOps], state_functions, []) - end. -%% Keep the last of each kind -collect_init_options([], CallbackMode, Ops) -> - {CallbackMode,lists:reverse(Ops)}; -collect_init_options( - [InitOp|InitOps] = AllInitOps, CallbackMode, Ops) -> - case InitOp of - {callback_mode,Mode} - when Mode =:= state_functions; - Mode =:= handle_event_function -> - collect_init_options(InitOps, Mode, Ops); - {callback_mode,_} -> - [{bad_init_ops,AllInitOps}]; - _ -> % Collect others as Ops - collect_init_options( - InitOps, CallbackMode, [InitOp|Ops]) - end. - collect_transition_options(Ops) -> if is_list(Ops) -> diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 65a8d35645..a8b4d16f23 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -36,7 +36,8 @@ all() -> {group, abnormal}, {group, abnormal_handle_event}, shutdown, - {group, sys}, hibernate, enter_loop]. + {group, sys}, + hibernate, enter_loop]. groups() -> [{start, [], @@ -73,7 +74,7 @@ init_per_group(GroupName, Config) GroupName =:= stop_handle_event; GroupName =:= abnormal_handle_event; GroupName =:= sys_handle_event -> - [{init_ops,[{callback_mode,handle_event_function}]}|Config]; + [{callback_mode,handle_event_function}|Config]; init_per_group(_GroupName, Config) -> Config. @@ -86,6 +87,8 @@ init_per_testcase(_CaseName, Config) -> %%% dbg:tracer(), %%% dbg:p(all, c), %%% dbg:tpl(gen_statem, cx), +%%% dbg:tpl(proc_lib, cx), +%%% dbg:tpl(gen, cx), %%% dbg:tpl(sys, cx), [{watchdog, Dog} | Config]. @@ -901,8 +904,11 @@ enter_loop(Config) when is_list(Config) -> end, %% Process not started using proc_lib + CallbackMode = state_functions, Pid4 = - spawn_link(gen_statem, enter_loop, [?MODULE,[],state0,[]]), + spawn_link( + gen_statem, enter_loop, + [?MODULE,[],CallbackMode,state0,[]]), receive {'EXIT',Pid4,process_was_not_started_by_proc_lib} -> ok @@ -976,16 +982,21 @@ enter_loop(Reg1, Reg2) -> anon -> ignore end, proc_lib:init_ack({ok, self()}), + CallbackMode = state_functions, case Reg2 of local -> - gen_statem:enter_loop(?MODULE, [], state0, [], {local,armitage}); + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], {local,armitage}); global -> - gen_statem:enter_loop(?MODULE, [], state0, [], {global,armitage}); + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], {global,armitage}); via -> - gen_statem:enter_loop(?MODULE, [], state0, [], - {via, dummy_via, armitage}); + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], + {via, dummy_via, armitage}); anon -> - gen_statem:enter_loop(?MODULE, [], state0, []) + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, []) end. %% @@ -1098,9 +1109,9 @@ verify_empty_msgq() -> ok. start_arg(Config, Arg) -> - case lists:keyfind(init_ops, 1, Config) of - {_,Ops} -> - {init_ops,Arg,Ops}; + case lists:keyfind(callback_mode, 1, Config) of + {_,CallbackMode} -> + {callback_mode,CallbackMode,Arg}; false -> Arg end. @@ -1119,24 +1130,24 @@ init(stop_shutdown) -> {stop,shutdown}; init(sleep) -> ?t:sleep(1000), - {ok,idle,data}; + {state_functions,idle,data}; init(hiber) -> - {ok,hiber_idle,[]}; + {state_functions,hiber_idle,[]}; init(hiber_now) -> - {ok,hiber_idle,[],[hibernate]}; + {state_functions,hiber_idle,[],[hibernate]}; init({data, Data}) -> - {ok,idle,Data}; -init({init_ops,Arg,InitOps}) -> + {state_functions,idle,Data}; +init({callback_mode,CallbackMode,Arg}) -> case init(Arg) of - {ok,State,Data,Ops} -> - {ok,State,Data,InitOps++Ops}; - {ok,State,Data} -> - {ok,State,Data,InitOps}; + {_,State,Data,Ops} -> + {CallbackMode,State,Data,Ops}; + {_,State,Data} -> + {CallbackMode,State,Data}; Other -> Other end; init([]) -> - {ok,idle,data}. + {state_functions,idle,data}. terminate(_, _State, crash_terminate) -> exit({crash,terminate}); |