From 99d48f5921f95073c8e46280494746b0e1a2c375 Mon Sep 17 00:00:00 2001 From: Raimo Niskanen Date: Tue, 16 Feb 2016 17:54:15 +0100 Subject: Implement option callback_mode --- lib/stdlib/doc/src/gen_statem.xml | 272 ++++++++++++++++++++++------------- lib/stdlib/src/gen_statem.erl | 134 +++++++++++------ lib/stdlib/test/gen_statem_SUITE.erl | 242 ++++++++++++++++++++++--------- 3 files changed, 439 insertions(+), 209 deletions(-) diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index f7ce0f61ae..f608e10469 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -31,8 +31,15 @@ gen_statem Generic State Machine Behaviour -

A behaviour module for implementing a state machine, primarily - a finite state machine, but an infinite state space is possible. +

A behaviour module for implementing a state machine. + Two callback modes are supported. One for a finite state + machine like gen_fsm + that require the state to be an atom and use that state as + the name of the callback function for a particular state, + and one without restriction on the state that use the same + callback function for all states. +

+

A generic state machine process (gen_statem) implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. @@ -55,7 +62,7 @@ gen_statem:stop -----> Module:terminate/2 gen_statem:call gen_statem:cast erlang:send -erlang:'!' -----> Module:State/5 +erlang:'!' -----> Module:StateName/5 Module:handle_event/5 - -----> Module:terminate/3 @@ -75,47 +82,39 @@ erlang:'!' -----> Module:State/5

The "state function" for a specific state in a gen_statem is the callback function that is called - for all events in this state. - An event can can be postponed causing it to be retried - after the state has changed, or more precisely; - after a state change all postponed events are retried - in the new state. + for all events in this state, and is selected depending on + callback_mode + that the implementation selects during gen_statem init.

-

The state machine - State - is normally an atom in which case the - state function - that will be called is - Module:State/5. - For a - State - that is not an atom the - state function - - Module:handle_event/5 - will be called. - If you use handle_event as a - state and later - decide to use non-atom states you will then have to - rewrite your code to stop using that state. +

When + callback_mode + is state_functions the state has to be an atom and + is used as the state function name. + See + + Module:StateName/5 + . + This naturally collects all code for a specific state + in one function and hence dispatches on state first.

-

When using an atom-only - State - it becomes fairly obvious in the implementation code - which events are handled in which state - since there are different callback functions for different states. +

When + callback_mode + is handle_event_function the state can be any term + and the state function name is + + Module:handle_event/5 + . + This makes it easy to dispatch on state or on event as + you like but you will have to implement it. + Also be careful about which events you handle in which + states so you do not accidentally postpone one event + forever creating an infinite busy loop.

- When using a non-atom State - all events are handled in the callback function - - Module:handle_event/5 - - so it may require more coding discipline to ensure what events - are handled in which state. Therefore it might be a wee bit - easier to accidentally postpone an event in two or more states - and not handling it in any of them causing a tight infinite - loop when the event bounces to be retried between the states. + An event can can be postponed causing it to be retried + after the state has changed, or more precisely; + after a state change all postponed events are retried + in the new state.

A gen_statem handles system messages as documented in sys. @@ -239,18 +238,22 @@ erlang:'!' -----> Module:State/5 -

If the gen_statem State is an atom(), the - state function is - Module:State/5. - If it is any other term() the - state function is - - Module:handle_event/5 - . After a state change (NewState =/= State) +

After a state change (NewState =/= State) all postponed events are retried.

+ + + +

If + + callback_mode + is state_functions, which is the default, + the state has to be of this type i.e an atom(). +

+
+
@@ -296,6 +299,33 @@ erlang:'!' -----> Module:State/5

+ + + +

Option that only is valid when initializing the gen_statem

+
+
+ + + + + state_functions + The state has to be of type + state_name() + and one callback function per state that is + + Module:StateName/5 + is used. This is the default. + + handle_event_function + The state can be any term and the callback function + + Module:handle_event/5 + is used for all states. + + + + @@ -472,6 +502,53 @@ erlang:'!' -----> Module:State/5 + + + + + + {stop,Reason,NewStateData} + + The same as + {stop,Reason,[],NewStateData} + + {stop, + Reason, + Replies, + NewStateData} + + The gen_statem will first send all + Replies and then terminate by calling + + Module:terminate/3 + with Reason. + + + + {next_state,NewState,NewStateData} + + + The same as + + {next_state,NewState,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 + + + + @@ -777,13 +854,16 @@ erlang:'!' -----> Module:State/5

Module, Options and Server have the same meanings as when calling - gen_statem:start[_link]/3,4. + + gen_statem:start[_link]/3,4 + . However, the server_name() name must have been registered accordingly before this function is called.

-

State and StateData +

State, StateData + and StateOps have the same meanings as in the return value of Module:init/1. Also, the callback module Module @@ -821,8 +901,13 @@ erlang:'!' -----> Module:State/5  | {ok,State,StateData,StateOps}  | {stop,Reason} | ignore State = state() - StateData = state_data() - StateOps = [state_op()] + StateData = + state_data() + + StateOps = + [state_op() + | init_option()] + Reason = term() @@ -843,10 +928,15 @@ erlang:'!' -----> Module:State/5 of the gen_statem.

The StateOps - are executed before entering the first + are executed when entering the first state just as for a state function.

+

This function allows an option to select the callback mode + of the gen_statem. See + init_option. + This option is not allowed from the state function(s). +

If something goes wrong during the initialization the function should return {stop,Reason} or ignore. See @@ -856,10 +946,10 @@ erlang:'!' -----> Module:State/5 - Module:handle_event(EventType, EventContent, - PrevState, State, StateData) -> Result + Module:StateName(EventType, EventContent, + PrevStateName, StateName, StateData) -> Result - Module:State(EventType, EventContent, + Module:handle_event(EventType, EventContent, PrevState, State, StateData) -> Result Handle an event @@ -868,41 +958,23 @@ erlang:'!' -----> Module:State/5 event_type() EventContent = term() - 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 = + PrevStateName = + state_name() + | reference() + + StateName = + state_name() + + PrevState = State = state() StateData = NewStateData = state_data() - Reply = - reply_operation() - - Replies = [Reply] - StateOps = - [state_op()] + Result = + + state_callback_result() + @@ -923,19 +995,18 @@ erlang:'!' -----> Module:State/5 gen_statem:reply(Client, Reply) .

-

State - is the internal state of the gen_statem which - when State is an atom() - is the same as this function's name, so it is seldom useful, - except for example when comparing with PrevState - that is the gen_statem's previous state, or in - - Module:handle_event/5 - since that function is common for all states - that are not an atom(). -

-

If this function returns with - NewState =/= State +

StateName is rarely useful. One exception is if + you call a common event handling function from your state + function then you might want to pass StateName. +

+

PrevStateName and PrevState are rarely useful. + They can be used when you want to do something only at the + first event in a state. Note that when gen_statem enters + its first state this is set to a reference() since + that can not match the state. +

+

If this function returns with a new state that + does not match equal (=/=) to the current state all postponed events will be retried in the new state.

See state_op() @@ -946,7 +1017,7 @@ erlang:'!' -----> Module:State/5 - Module:terminate(Reason, State, StateData) + Module:terminate(Reason, State, StateData) -> Ignored Clean up before termination Reason = normal | shutdown | {shutdown,term()} | term() @@ -956,6 +1027,7 @@ erlang:'!' -----> Module:State/5 state_data() + Ignored = term()

This function is called by a gen_statem when it is about to diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 7c3cd8c2f3..e6bb38fc2c 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -44,6 +44,9 @@ -export( [wakeup_from_hibernate/3]). +%% Fix problem for doc build +-export_type([state_callback_result/0]). + %%%========================================================================== %%% Interface functions. %%%========================================================================== @@ -51,14 +54,18 @@ -type client() :: {To :: pid(), Tag :: term()}. % Reply-to specifier for call -type state() :: - atom() | % Calls state callback function State/5 - term(). % Calls state callback function handle_event/5 + state_name() | % For state callback function StateName/5 + term(). % For state callback function handle_event/5 +-type state_name() :: atom(). -type state_data() :: term(). -type event_type() :: {'call',Client :: client()} | 'cast' | '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 state_op() :: %% First NewState and NewStateData are set, %% then all state_operations() are executed in order of @@ -97,6 +104,21 @@ -type reply_operation() :: {'reply', % Reply to a client Client :: client(), Reply :: term()}. +-type state_callback_result() :: + {stop, % Stop the server + Reason :: term(), + NewStateData :: state_data()} | + {stop, % Stop the server + Reason :: term(), + Replies :: [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(), + StateOps :: [state_op()] | state_op()}. %% The state machine init function. It is called only once and %% the server is not running until this function has returned @@ -104,40 +126,36 @@ %% for all events to this server. -callback init(Args :: term()) -> {'ok', state(), state_data()} | - {'ok', state(), state_data(), [state_op()]} | + {'ok', state(), state_data(), [state_op()|init_option()]} | 'ignore' | {'stop', Reason :: term()}. -%% An example callback for a fictive state 'handle_event' -%% that you should avoid having. See below. +%% Example callback for callback_mode =:= state_functions +%% state name 'state_name'. +%% +%% In this mode all states has to be type state_name() i.e atom(). %% %% Note that state callbacks and only state callbacks have arity 5 %% and that is intended. +-callback state_name( + event_type(), + EventContent :: term(), + PrevStateName :: state_name() | reference(), + StateName :: state_name(), % Current state + StateData :: state_data()) -> + state_callback_result(). +%% +%% Callback for callback_mode =:= handle_event_function. %% -%% You should not actually use 'handle_event' as a state name, -%% since it is the callback function that is used if you would use -%% a State that is not an atom(). This is because since there is -%% no obvious way to decide on a state function name from any term(). +%% Note that state callbacks and only state callbacks have arity 5 +%% and that is intended. -callback handle_event( event_type(), EventContent :: term(), PrevState :: state(), State :: state(), % Current state StateData :: state_data()) -> - {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()}. + state_callback_result(). %% Clean up before the server terminates. -callback terminate( @@ -171,8 +189,11 @@ -optional_callbacks( [format_status/2, % Has got a default implementation - handle_event/5]). % Only needed for State not an atom() -%% For every atom() State there has to be a State/5 callback function + %% + state_name/5, % 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 %% Type validation functions client({Pid,Tag}) when is_pid(Pid), is_reference(Tag) -> @@ -366,7 +387,8 @@ enter_loop(Module, Options, State, StateData) -> -spec enter_loop( Module :: module(), Options :: [debug_opt()], State :: state(), StateData :: state_data(), - Server_or_StateOps :: server_name() | pid() | [state_op()]) -> + Server_or_StateOps :: + server_name() | pid() | [state_op()|init_option()]) -> no_return(). enter_loop(Module, Options, State, StateData, Server_or_StateOps) -> if @@ -384,7 +406,7 @@ enter_loop(Module, Options, State, StateData, Server_or_StateOps) -> Module :: module(), Options :: [debug_opt()], State :: state(), StateData :: state_data(), Server :: server_name() | pid(), - StateOps :: [state_op()]) -> + StateOps :: [state_op()|init_option()]) -> no_return(). enter_loop(Module, Options, State, StateData, Server, StateOps) -> Parent = gen:get_parent(), @@ -410,11 +432,12 @@ do_send(Proc, Msg) -> end. %% Here init_it and all enter_loop functions converge -enter(Module, Options, State, StateData, Server, StateOps, Parent) -> +enter(Module, Options, State, StateData, Server, InitOps, Parent) -> Name = gen:get_proc_name(Server), Debug = gen:debug_options(Name, Options), PrevState = make_ref(), S = #{ + callback_mode => state_functions, module => Module, name => Name, prev_state => PrevState, @@ -423,9 +446,17 @@ enter(Module, Options, State, StateData, Server, StateOps, Parent) -> timer => undefined, postponed => [], hibernate => false}, - loop_event_state_ops( - Parent, Debug, S, [], {event,undefined}, - State, StateData, [{retry,false}|StateOps]). + case collect_init_options(InitOps) of + {CallbackMode,StateOps} -> + loop_event_state_ops( + Parent, Debug, + S#{callback_mode := CallbackMode}, + [], {event,undefined}, + State, StateData, + [{retry,false}|StateOps]); + [Reason] -> + terminate(Reason, Debug, S, []) + end. %%%========================================================================== %%% gen callbacks @@ -643,13 +674,13 @@ loop_receive(Parent, Debug, S, Event, Timer) -> %% The loop_event* functions optimize S map handling by dismantling it, %% passing the parts in arguments to avoid map lookups and construct the %% new S map in one go on exit. Premature optimization, I know, but -%% the code was way to readable and there were quite some map lookups -%% repeated in different functions. +%% there were quite some map lookups repeated in different functions. loop_events(Parent, Debug, S, [], _Timer) -> loop(Parent, Debug, S); loop_events( Parent, Debug, - #{module := Module, + #{callback_mode := CallbackMode, + module := Module, prev_state := PrevState, state := State, state_data := StateData} = S, @@ -657,11 +688,11 @@ loop_events( _ = (Timer =/= undefined) andalso cancel_timer(Timer), Func = - if - is_atom(State) -> - State; - true -> - handle_event + case CallbackMode of + handle_event_function -> + handle_event; + state_functions -> + State end, try Module:Func(Type, Content, PrevState, State, StateData) of Result -> @@ -675,7 +706,10 @@ loop_events( %% Process an undef to check for the simple mistake %% of calling a nonexistent state function case erlang:get_stacktrace() of - [{Module,State,[Event,StateData]=Args,_}|Stacktrace] -> + [{Module,Func, + [Type,Content,PrevState,State,StateData]=Args, + _} + |Stacktrace] -> terminate( error, {undef_state_function,{Module,State,Args}}, @@ -709,7 +743,7 @@ loop_event_result(Parent, Debug, S, Events, Event, Result) -> end, BadReplies = reply_then_terminate(Reason, Debug, NewS, Q, Replies), - %% Since it returned Replies was bad + %% Since we got back here Replies was bad terminate( {bad_return_value,{stop,Reason,BadReplies,NewStateData}}, Debug, NewS, Q); @@ -794,6 +828,24 @@ loop_event_state_ops( %%--------------------------------------------------------------------------- %% Server helpers +collect_init_options(InitOps) -> + collect_init_options(lists:reverse(InitOps), state_functions, []). +%% Keep the last of each kind +collect_init_options([], CallbackMode, StateOps) -> + {CallbackMode,StateOps}; +collect_init_options([InitOp|InitOps] = IOIOs, CallbackMode, StateOps) -> + case InitOp of + {callback_mode,Mode} + when Mode =:= state_functions; + Mode =:= handle_event_function -> + collect_init_options(InitOps, Mode, StateOps); + {callback_mode,_} -> + [{bad_init_ops,IOIOs}]; + _ -> % Collect others as StateOps + collect_init_options( + InitOps, CallbackMode, [InitOp|StateOps]) + end. + collect_state_options(StateOps) -> collect_state_options( lists:reverse(StateOps), false, false, undefined, []). diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 51e08f5ec1..8a96f0e8e2 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -19,7 +19,7 @@ %% -module(gen_statem_SUITE). --include_lib("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct.hrl"). -compile(export_all). -behaviour(gen_statem). @@ -30,7 +30,11 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [{group, start}, + {group, start_handle_event}, + {group, stop}, + {group, stop_handle_event}, {group, abnormal}, + {group, abnormal_handle_event}, shutdown, {group, sys}, hibernate, enter_loop]. @@ -38,10 +42,21 @@ groups() -> [{start, [], [start1, start2, start3, start4, start5, start6, start7, start8, start9, start10, start11, start12]}, + {start_handle_event, [], + [start1, start2, start3, start4, start5, start6, start7, + start8, start9, start10, start11, start12]}, {stop, [], [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, + {stop_handle_event, [], + [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, {abnormal, [], [abnormal1, abnormal2]}, + {abnormal_handle_event, [], [abnormal1, abnormal2]}, {sys, [], + [sys1, + call_format_status, + error_format_status, terminate_crash_format, + get_state, replace_state]}, + {sys_handle_event, [], [sys1, call_format_status, error_format_status, terminate_crash_format, @@ -53,6 +68,12 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_group(GroupName, Config) + when GroupName =:= start_handle_event; + GroupName =:= stop_handle_event; + GroupName =:= abnormal_handle_event; + GroupName =:= sys_handle_event -> + [{init_ops,[{callback_mode,handle_event_function}]}|Config]; init_per_group(_GroupName, Config) -> Config. @@ -65,6 +86,7 @@ init_per_testcase(_CaseName, Config) -> %%% dbg:tracer(), %%% dbg:p(all, c), %%% dbg:tpl(gen_statem, cx), +%%% dbg:tpl(sys, cx), [{watchdog, Dog} | Config]. end_per_testcase(_CaseName, Config) -> @@ -88,7 +110,7 @@ end_per_testcase(_CaseName, Config) -> start1(Config) when is_list(Config) -> %%OldFl = process_flag(trap_exit, true), - {ok,Pid0} = gen_statem:start_link(?MODULE, [], []), + {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid0), ok = do_sync_func_test(Pid0), stop_it(Pid0), @@ -102,7 +124,7 @@ start1(Config) when is_list(Config) -> %% anonymous w. shutdown start2(Config) when is_list(Config) -> %% Dont link when shutdown - {ok,Pid0} = gen_statem:start(?MODULE, [], []), + {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid0), ok = do_sync_func_test(Pid0), stopped = gen_statem:call(Pid0, {stop,shutdown}), @@ -113,13 +135,15 @@ start2(Config) when is_list(Config) -> start3(Config) when is_list(Config) -> %%OldFl = process_flag(trap_exit, true), - {ok,Pid0} = gen_statem:start(?MODULE, [], [{timeout,5}]), + {ok,Pid0} = + gen_statem:start(?MODULE, start_arg(Config, []), [{timeout,5}]), ok = do_func_test(Pid0), ok = do_sync_func_test(Pid0), stop_it(Pid0), - {error,timeout} = gen_statem:start(?MODULE, sleep, - [{timeout,5}]), + {error,timeout} = + gen_statem:start( + ?MODULE, start_arg(Config, sleep), [{timeout,5}]), %%process_flag(trap_exit, OldFl), ok = verify_empty_msgq(). @@ -128,7 +152,7 @@ start3(Config) when is_list(Config) -> start4(Config) when is_list(Config) -> OldFl = process_flag(trap_exit, true), - ignore = gen_statem:start(?MODULE, ignore, []), + ignore = gen_statem:start(?MODULE, start_arg(Config, ignore), []), process_flag(trap_exit, OldFl), ok = verify_empty_msgq(). @@ -138,14 +162,14 @@ start5(suite) -> []; start5(Config) when is_list(Config) -> OldFl = process_flag(trap_exit, true), - {error,stopped} = gen_statem:start(?MODULE, stop, []), + {error,stopped} = gen_statem:start(?MODULE, start_arg(Config, stop), []), process_flag(trap_exit, OldFl), ok = verify_empty_msgq(). %% anonymous linked start6(Config) when is_list(Config) -> - {ok,Pid} = gen_statem:start_link(?MODULE, [], []), + {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid), ok = do_sync_func_test(Pid), stop_it(Pid), @@ -157,11 +181,11 @@ start7(Config) when is_list(Config) -> STM = {global,my_stm}, {ok,Pid} = - gen_statem:start_link(STM, ?MODULE, [], []), + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start_link(STM, ?MODULE, [], []), + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start(STM, ?MODULE, [], []), + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid), ok = do_sync_func_test(Pid), @@ -179,9 +203,9 @@ start8(Config) when is_list(Config) -> STM = {local,Name}, {ok,Pid} = - gen_statem:start(STM, ?MODULE, [], []), + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start(STM, ?MODULE, [], []), + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid), ok = do_sync_func_test(Pid), @@ -199,9 +223,9 @@ start9(Config) when is_list(Config) -> STM = {local,Name}, {ok,Pid} = - gen_statem:start_link(STM, ?MODULE, [], []), + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start(STM, ?MODULE, [], []), + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid), ok = do_sync_func_test(Pid), @@ -217,11 +241,11 @@ start10(Config) when is_list(Config) -> STM = {global,my_stm}, {ok,Pid} = - gen_statem:start(STM, ?MODULE, [], []), + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start(STM, ?MODULE, [], []), + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start_link(STM, ?MODULE, [], []), + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid), ok = do_sync_func_test(Pid), @@ -238,19 +262,19 @@ start11(Config) when is_list(Config) -> GlobalSTM = {global,Name}, {ok,Pid} = - gen_statem:start_link(LocalSTM, ?MODULE, [], []), + gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), stop_it(Pid), {ok,_Pid1} = - gen_statem:start_link(LocalSTM, ?MODULE, [], []), + gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), stop_it(Name), {ok,Pid2} = - gen_statem:start(GlobalSTM, ?MODULE, [], []), + gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), stop_it(Pid2), receive after 1 -> true end, Result = - gen_statem:start(GlobalSTM, ?MODULE, [], []), + gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), io:format("Result = ~p~n",[Result]), {ok,_Pid3} = Result, stop_it(GlobalSTM), @@ -263,11 +287,11 @@ start12(Config) when is_list(Config) -> VIA = {via,dummy_via,my_stm}, {ok,Pid} = - gen_statem:start_link(VIA, ?MODULE, [], []), + gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start_link(VIA, ?MODULE, [], []), + gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), {error,{already_started,Pid}} = - gen_statem:start(VIA, ?MODULE, [], []), + gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid), ok = do_sync_func_test(Pid), @@ -279,23 +303,23 @@ start12(Config) when is_list(Config) -> %% Anonymous, reason 'normal' -stop1(_Config) -> - {ok,Pid} = gen_statem:start(?MODULE, [], []), +stop1(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), ok = gen_statem:stop(Pid), false = erlang:is_process_alive(Pid), noproc = ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). %% Anonymous, other reason -stop2(_Config) -> - {ok,Pid} = gen_statem:start(?MODULE, [], []), +stop2(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), ok = gen_statem:stop(Pid, other_reason, infinity), false = erlang:is_process_alive(Pid), ok. %% Anonymous, invalid timeout -stop3(_Config) -> - {ok,Pid} = gen_statem:start(?MODULE, [], []), +stop3(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), _ = ?EXPECT_FAILURE( gen_statem:stop(Pid, other_reason, invalid_timeout), @@ -306,8 +330,10 @@ stop3(_Config) -> ok. %% Registered name -stop4(_Config) -> - {ok,Pid} = gen_statem:start({local,to_stop},?MODULE, [], []), +stop4(Config) -> + {ok,Pid} = + gen_statem:start( + {local,to_stop},?MODULE, start_arg(Config, []), []), ok = gen_statem:stop(to_stop), false = erlang:is_process_alive(Pid), noproc = @@ -315,9 +341,11 @@ stop4(_Config) -> ok. %% Registered name and local node -stop5(_Config) -> +stop5(Config) -> Name = to_stop, - {ok,Pid} = gen_statem:start({local,Name},?MODULE, [], []), + {ok,Pid} = + gen_statem:start( + {local,Name},?MODULE, start_arg(Config, []), []), ok = gen_statem:stop({Name,node()}), false = erlang:is_process_alive(Pid), noproc = @@ -325,9 +353,9 @@ stop5(_Config) -> ok. %% Globally registered name -stop6(_Config) -> +stop6(Config) -> STM = {global,to_stop}, - {ok,Pid} = gen_statem:start(STM, ?MODULE, [], []), + {ok,Pid} = gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), ok = gen_statem:stop(STM), false = erlang:is_process_alive(Pid), noproc = @@ -335,11 +363,10 @@ stop6(_Config) -> ok. %% 'via' registered name -stop7(_Config) -> +stop7(Config) -> VIA = {via,dummy_via,to_stop}, dummy_via:reset(), - {ok,Pid} = gen_statem:start(VIA, - ?MODULE, [], []), + {ok,Pid} = gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), ok = gen_statem:stop(VIA), false = erlang:is_process_alive(Pid), noproc = @@ -347,46 +374,55 @@ stop7(_Config) -> ok. %% Anonymous on remote node -stop8(_Config) -> +stop8(Config) -> {ok,Node} = ?t:start_node(gen_statem_stop8, slave, []), Dir = filename:dirname(code:which(?MODULE)), rpc:call(Node, code, add_path, [Dir]), - {ok,Pid} = rpc:call(Node, gen_statem,start, [?MODULE,[],[]]), + {ok,Pid} = + rpc:call( + Node, gen_statem,start, + [?MODULE,start_arg(Config, []),[]]), ok = gen_statem:stop(Pid), false = rpc:call(Node, erlang, is_process_alive, [Pid]), noproc = ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), true = ?t:stop_node(Node), - {nodedown,Node} = + {{nodedown,Node},{sys,terminate,_}} = ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2), ok. %% Registered name on remote node -stop9(_Config) -> +stop9(Config) -> Name = to_stop, LocalSTM = {local,Name}, {ok,Node} = ?t:start_node(gen_statem__stop9, slave, []), STM = {Name,Node}, Dir = filename:dirname(code:which(?MODULE)), rpc:call(Node, code, add_path, [Dir]), - {ok,Pid} = rpc:call(Node, gen_statem, start, [LocalSTM,?MODULE,[],[]]), + {ok,Pid} = + rpc:call( + Node, gen_statem, start, + [LocalSTM,?MODULE,start_arg(Config, []),[]]), ok = gen_statem:stop(STM), undefined = rpc:call(Node,erlang,whereis,[Name]), false = rpc:call(Node,erlang,is_process_alive,[Pid]), noproc = ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), true = ?t:stop_node(Node), - {nodedown,Node} = + {{nodedown,Node},{sys,terminate,_}} = ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), ok. %% Globally registered name on remote node -stop10(_Config) -> +stop10(Config) -> STM = {global,to_stop}, {ok,Node} = ?t:start_node(gen_statem_stop10, slave, []), Dir = filename:dirname(code:which(?MODULE)), rpc:call(Node,code,add_path,[Dir]), - {ok,Pid} = rpc:call(Node, gen_statem, start, [STM,?MODULE,[],[]]), + {ok,Pid} = + rpc:call( + Node, gen_statem, start, + [STM,?MODULE,start_arg(Config, []),[]]), global:sync(), ok = gen_statem:stop(STM), false = rpc:call(Node, erlang, is_process_alive, [Pid]), @@ -402,7 +438,8 @@ abnormal1(Config) when is_list(Config) -> Name = abnormal1, LocalSTM = {local,Name}, - {ok, _Pid} = gen_statem:start(LocalSTM, ?MODULE, [], []), + {ok, _Pid} = + gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), %% timeout call. delayed = gen_statem:call(Name, {delayed_answer,1}, 100), @@ -410,13 +447,14 @@ abnormal1(Config) when is_list(Config) -> ?EXPECT_FAILURE( gen_statem:call(Name, {delayed_answer,1000}, 10), Reason), + ok = gen_statem:stop(Name), ok = verify_empty_msgq(). %% Check that bad return values makes the stm crash. Note that we must %% trap exit since we must link to get the real bad_return_ error abnormal2(Config) when is_list(Config) -> OldFl = process_flag(trap_exit, true), - {ok,Pid} = gen_statem:start_link(?MODULE, [], []), + {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), %% bad return value in the gen_statem loop {{bad_return_value,badreturn},_} = @@ -433,7 +471,7 @@ abnormal2(Config) when is_list(Config) -> shutdown(Config) when is_list(Config) -> process_flag(trap_exit, true), - {ok,Pid0} = gen_statem:start_link(?MODULE, [], []), + {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), ok = do_func_test(Pid0), ok = do_sync_func_test(Pid0), stopped = gen_statem:call(Pid0, {stop,{shutdown,reason}}), @@ -454,7 +492,7 @@ shutdown(Config) when is_list(Config) -> sys1(Config) when is_list(Config) -> - {ok,Pid} = gen_statem:start(?MODULE, [], []), + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid), sys:suspend(Pid), Parent = self(), @@ -477,7 +515,7 @@ sys1(Config) when is_list(Config) -> stop_it(Pid). call_format_status(Config) when is_list(Config) -> - {ok,Pid} = gen_statem:start(?MODULE, [], []), + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), Status = sys:get_status(Pid), {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status, [format_status_called|_] = lists:reverse(Data), @@ -485,7 +523,9 @@ call_format_status(Config) when is_list(Config) -> %% check that format_status can handle a name being an atom (pid is %% already checked by the previous test) - {ok, Pid2} = gen_statem:start({local, gstm}, ?MODULE, [], []), + {ok, Pid2} = + gen_statem:start( + {local, gstm}, ?MODULE, start_arg(Config, []), []), Status2 = sys:get_status(gstm), {status,Pid2,_Mod,[_PDict2,running,_,_,Data2]} = Status2, [format_status_called|_] = lists:reverse(Data2), @@ -494,13 +534,17 @@ call_format_status(Config) when is_list(Config) -> %% check that format_status can handle a name being a term other than a %% pid or atom GlobalName1 = {global,"CallFormatStatus"}, - {ok,Pid3} = gen_statem:start(GlobalName1, ?MODULE, [], []), + {ok,Pid3} = + gen_statem:start( + GlobalName1, ?MODULE, start_arg(Config, []), []), Status3 = sys:get_status(GlobalName1), {status,Pid3,_Mod,[_PDict3,running,_,_,Data3]} = Status3, [format_status_called|_] = lists:reverse(Data3), stop_it(Pid3), GlobalName2 = {global,{name, "term"}}, - {ok,Pid4} = gen_statem:start(GlobalName2, ?MODULE, [], []), + {ok,Pid4} = + gen_statem:start( + GlobalName2, ?MODULE, start_arg(Config, []), []), Status4 = sys:get_status(GlobalName2), {status,Pid4,_Mod,[_PDict4,running,_,_, Data4]} = Status4, [format_status_called|_] = lists:reverse(Data4), @@ -510,13 +554,15 @@ call_format_status(Config) when is_list(Config) -> %% pid or atom dummy_via:reset(), ViaName1 = {via,dummy_via,"CallFormatStatus"}, - {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, [], []), + {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []), Status5 = sys:get_status(ViaName1), {status,Pid5,_Mod, [_PDict5,running,_,_, Data5]} = Status5, [format_status_called|_] = lists:reverse(Data5), stop_it(Pid5), ViaName2 = {via,dummy_via,{name,"term"}}, - {ok, Pid6} = gen_statem:start(ViaName2, ?MODULE, [], []), + {ok, Pid6} = + gen_statem:start( + ViaName2, ?MODULE, start_arg(Config, []), []), Status6 = sys:get_status(ViaName2), {status,Pid6,_Mod,[_PDict6,running,_,_,Data6]} = Status6, [format_status_called|_] = lists:reverse(Data6), @@ -528,7 +574,9 @@ error_format_status(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), StateData = "called format_status", - {ok,Pid} = gen_statem:start(?MODULE, {state_data,StateData}, []), + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {state_data,StateData}), []), %% bad return value in the gen_statem loop {{bad_return_value,badreturn},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), @@ -562,7 +610,9 @@ terminate_crash_format(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), StateData = crash_terminate, - {ok,Pid} = gen_statem:start(?MODULE, {state_data,StateData}, []), + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {state_data,StateData}), []), stop_it(Pid), Self = self(), receive @@ -595,7 +645,9 @@ terminate_crash_format(Config) when is_list(Config) -> get_state(Config) when is_list(Config) -> State = self(), - {ok,Pid} = gen_statem:start(?MODULE, {state_data,State}, []), + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {state_data,State}), []), {idle,State} = sys:get_state(Pid), {idle,State} = sys:get_state(Pid, 5000), stop_it(Pid), @@ -603,13 +655,16 @@ get_state(Config) when is_list(Config) -> %% check that get_state can handle a name being an atom (pid is %% already checked by the previous test) {ok,Pid2} = - gen_statem:start({local,gstm}, ?MODULE, {state_data,State}, []), + gen_statem:start( + {local,gstm}, ?MODULE, start_arg(Config, {state_data,State}), []), {idle,State} = sys:get_state(gstm), {idle,State} = sys:get_state(gstm, 5000), stop_it(Pid2), %% check that get_state works when pid is sys suspended - {ok,Pid3} = gen_statem:start(?MODULE, {state_data,State}, []), + {ok,Pid3} = + gen_statem:start( + ?MODULE, start_arg(Config, {state_data,State}), []), {idle,State} = sys:get_state(Pid3), ok = sys:suspend(Pid3), {idle,State} = sys:get_state(Pid3, 5000), @@ -619,7 +674,9 @@ get_state(Config) when is_list(Config) -> replace_state(Config) when is_list(Config) -> State = self(), - {ok, Pid} = gen_statem:start(?MODULE, {state_data,State}, []), + {ok, Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {state_data,State}), []), {idle,State} = sys:get_state(Pid), NState1 = "replaced", Replace1 = fun({StateName, _}) -> {StateName,NState1} end, @@ -650,7 +707,9 @@ replace_state(Config) when is_list(Config) -> hibernate(Config) when is_list(Config) -> OldFl = process_flag(trap_exit, true), - {ok,Pid0} = gen_statem:start_link(?MODULE, hiber_now, []), + {ok,Pid0} = + gen_statem:start_link( + ?MODULE, start_arg(Config, hiber_now), []), is_in_erlang_hibernate(Pid0), stop_it(Pid0), receive @@ -659,7 +718,8 @@ hibernate(Config) when is_list(Config) -> ?t:fail(gen_statem_did_not_die) end, - {ok,Pid} = gen_statem:start_link(?MODULE, hiber, []), + {ok,Pid} = + gen_statem:start_link(?MODULE, start_arg(Config, hiber), []), true = ({current_function,{erlang,hibernate,3}} =/= erlang:process_info(Pid,current_function)), hibernating = gen_statem:call(Pid, hibernate_sync), @@ -1031,6 +1091,14 @@ verify_empty_msgq() -> [] = ?t:messages_get(), ok. +start_arg(Config, Arg) -> + case lists:keyfind(init_ops, 1, Config) of + {_,Ops} -> + {init_ops,Arg,Ops}; + false -> + Arg + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% The State Machine @@ -1052,7 +1120,16 @@ init(hiber_now) -> {ok,hiber_idle,[],[hibernate]}; init({state_data, StateData}) -> {ok,idle,StateData}; -init(_) -> +init({init_ops,Arg,InitOps}) -> + case init(Arg) of + {ok,State,Data,Ops} -> + {ok,State,Data,InitOps++Ops}; + {ok,State,Data} -> + {ok,State,Data,InitOps}; + Other -> + Other + end; +init([]) -> {ok,idle,state_data}. terminate(_, _State, crash_terminate) -> @@ -1247,6 +1324,35 @@ handle_common_events(cast, {'alive?',Pid}, _, State, Data) -> handle_common_events(_, _, _, _, _) -> undefined. +%% Dispatcher to test callback_mode handle_event_function +%% +%% 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(PrevState), + StateName = unwrap(State), + case + ?MODULE:StateName( + Type, Event, PrevStateName, StateName, Data) of + {next_state,NewState,NewStateData} -> + {next_state,wrap(NewState),NewStateData}; + {next_state,NewState,NewStateData,StateOps} -> + {next_state,wrap(NewState),NewStateData,StateOps}; + Other -> + Other + end. + +unwrap([State]) -> + State; +unwrap(State) -> + State. + +wrap(State) -> + [State]. + + + code_change(_OldVsn, State, StateData, _Extra) -> {ok,State,StateData}. -- cgit v1.2.3