diff options
author | Raimo Niskanen <[email protected]> | 2016-04-21 15:58:52 +0200 |
---|---|---|
committer | Raimo Niskanen <[email protected]> | 2016-04-21 15:58:52 +0200 |
commit | 26b3c7d60d52d8a7be006b06d856bb0f7276e77a (patch) | |
tree | 82fe6796d1139babe79073d7c4e392d7d7a5dc90 | |
parent | 2977fbc6b658b0d664f7d3b36ecf8ca9e897aaa3 (diff) | |
download | otp-26b3c7d60d52d8a7be006b06d856bb0f7276e77a.tar.gz otp-26b3c7d60d52d8a7be006b06d856bb0f7276e77a.tar.bz2 otp-26b3c7d60d52d8a7be006b06d856bb0f7276e77a.zip |
Modify code_change/4 to return CallbackMode
Also move check of non-atom states in callback mode
state_functions to where the state function is called.
This gives homogenous diagnostics for state functions,
code_change/4 and system_replace_state StateFun.
Irregularities pointed out by James Fish.
-rw-r--r-- | lib/stdlib/doc/src/gen_statem.xml | 71 | ||||
-rw-r--r-- | lib/stdlib/src/gen_statem.erl | 60 | ||||
-rw-r--r-- | lib/stdlib/test/gen_statem_SUITE.erl | 8 | ||||
-rw-r--r-- | system/doc/design_principles/statem.xml | 87 |
4 files changed, 128 insertions, 98 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index c21398d64d..adca3673be 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -32,8 +32,9 @@ <modulesummary>Generic State Machine Behaviour</modulesummary> <description> <p> - A behaviour module for implementing a state machine. - Two callback modes are supported. One for a finite state + A behaviour module for implementing a state machine. Two + <seealso marker="#type-callback_mode"><em>callback modes</em></seealso> + are supported. One for a finite state machine like <seealso marker="gen_fsm">gen_fsm</seealso> that requires the state to be an atom and use that state as the name of the callback function for a particular state, @@ -89,13 +90,13 @@ erlang:'!' -----> Module:StateName/3 The "<em>state function</em>" for a specific <seealso marker="#type-state">state</seealso> in a <c>gen_statem</c> is the callback function that is called - for all events in this state, and is selected depending on - <seealso marker="#type-callback_mode">callback_mode</seealso> + for all events in this state, and is selected depending on which + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> that the implementation specifies when the the server starts. </p> <p> - When - <seealso marker="#type-callback_mode">callback_mode</seealso> + When the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> 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/3"> @@ -110,7 +111,8 @@ erlang:'!' -----> Module:StateName/3 </seealso> makes the state name <c>terminate</c> unusable. </p> <p> - When <seealso marker="#type-callback_mode">callback_mode</seealso> + When the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> is <c>handle_event_function</c> the state can be any term and the state function name is <seealso marker="#Module:handle_event/4"> @@ -208,9 +210,7 @@ erlang:'!' -----> Module:StateName/3 <p> This example shows a simple pushbutton model for a toggling pushbutton implemented with - <seealso marker="#type-callback_mode"> - callback_mode() - </seealso> + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> <c>state_functions</c>. You can push the button and it replies if it went on or off, and you can ask for a count of how many times it has been @@ -226,6 +226,7 @@ erlang:'!' -----> Module:StateName/3 -export([on/3,off/3]). name() -> pushbutton_statem. % The registered server name +callback_mode() -> state_functions. %% API. This example uses a registered name name() %% and does not link to the caller. @@ -242,12 +243,12 @@ stop() -> terminate(_Reason, _State, _Data) -> void. code_change(_Vsn, State, Data, _Extra) -> - {ok,State,Data}. + {callback_mode(),State,Data}. init([]) -> %% Set the callback mode and initial state + data. %% Data is used only as a counter. State = off, Data = 0, - {state_functions,State,Data}. + {callback_mode(),State,Data}. %%% State functions @@ -297,9 +298,7 @@ ok <p> And just to compare styles here is the same example using - <seealso marker="#type-callback_mode"> - <c>callback_mode</c> - </seealso> + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> <c>state_functions</c>, or rather here is code to replace from the <c>init/1</c> function of the <c>pushbutton.erl</c> example file above: @@ -453,10 +452,8 @@ handle_event(_, _, State, Data) -> <name name="state_name" /> <desc> <p> - If - <seealso marker="#type-callback_mode"> - callback_mode - </seealso> + If the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> is <c>state_functions</c>, the state has to be of this type. </p> @@ -499,7 +496,7 @@ handle_event(_, _, State, Data) -> <name name="callback_mode" /> <desc> <p> - The <c>callback_mode</c> is selected when starting the + The <em>callback mode</em> is selected when starting the <c>gen_statem</c> using the return value from <seealso marker="#Module:init/1">Module:init/1</seealso> or when calling @@ -1277,7 +1274,7 @@ handle_event(_, _, State, Data) -> return <c>{CallbackMode,State,Data}</c> or <c>{CallbackMode,State,Data,Actions}</c>. <c>CallbackMode</c> selects the - <seealso marker="#type-callback_mode">callback_mode()</seealso>. + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso>. of the <c>gen_statem</c>. <c>State</c> is the initial <seealso marker="#type-state">state()</seealso> @@ -1344,8 +1341,8 @@ handle_event(_, _, State, Data) -> Whenever a <c>gen_statem</c> receives an event from <seealso marker="#call/2">call/2</seealso>, <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> + as a normal process message one of these functions is called. If + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> 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/4</c> is called. @@ -1468,7 +1465,11 @@ handle_event(_, _, State, Data) -> <v> Vsn = term()</v> <v>OldState = NewState = term()</v> <v>Extra = term()</v> - <v>Result = {ok,NewState,NewData} | Reason</v> + <v>Result = {NewCallbackMode,NewState,NewData} | Reason</v> + <v> + NewCallbackMode = + <seealso marker="#type-callback_mode">callback_mode()</seealso> + </v> <v> OldState = NewState = <seealso marker="#type-state">state()</seealso> @@ -1484,8 +1485,9 @@ handle_event(_, _, State, Data) -> This function is called by a <c>gen_statem</c> when it should update its internal state during a release upgrade/downgrade, that is when the instruction <c>{update,Module,Change,...}</c> - where <c>Change={advanced,Extra}</c> is given in - the <c>appup</c> file. See + where <c>Change={advanced,Extra}</c> is given in the + <seealso marker="sasl:appup"><c>appup</c></seealso> + file. See <seealso marker="doc/design_principles:release_handling#instr"> OTP Design Principles </seealso> @@ -1499,6 +1501,21 @@ handle_event(_, _, State, Data) -> <c>Module</c>. If no such attribute is defined, the version is the checksum of the BEAM file. </p> + <note> + <p> + If you would dare to change + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + during release upgrade/downgrade, the upgrade is no problem + since the new code surely knows what <em>callback mode</em> + it needs, but for a downgrade this function will have to + know from the <c>Extra</c> argument that comes from the + <seealso marker="sasl:appup"><c>appup</c></seealso> + file what <em>callback mode</em> the old code did use. + It may also be possible to figure this out + from the <c>{down,Vsn}</c> argument since <c>Vsn</c> + in effect defines the old callback module version. + </p> + </note> <p> <c>OldState</c> and <c>OldData</c> is the internal state of the <c>gen_statem</c>. @@ -1510,7 +1527,7 @@ handle_event(_, _, State, Data) -> <p> If successful, the function shall return the updated internal state in an - <c>{ok,NewState,NewData}</c> tuple. + <c>{NewCallbackMode,NewState,NewData}</c> tuple. </p> <p> If the function returns <c>Reason</c>, the ongoing diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index b3a149a569..c85c521d8e 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -219,7 +219,9 @@ OldState :: state(), OldData :: data(), Extra :: term()) -> - {ok, NewState :: state(), NewData :: data()}. + {NewCallbackMode :: callback_mode(), + NewState :: state(), + NewData :: data()}. %% Format the callback module state in some sensible that is %% often condensed way. For StatusOption =:= 'normal' the perferred @@ -491,17 +493,10 @@ enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Actions) -> Actions :: [action()] | action()) -> no_return(). enter_loop(Module, Opts, CallbackMode, State, Data, Server, Actions) -> - case is_atom(Module) andalso callback_mode(CallbackMode) of - true -> - Parent = gen:get_parent(), - enter( - Module, Opts, CallbackMode, State, Data, Server, - Actions, Parent); - false -> - error( - badarg, - [Module,Opts,CallbackMode,State,Data,Server,Actions]) - end. + is_atom(Module) orelse error({atom,Module}), + callback_mode(CallbackMode) orelse error({callback_mode,CallbackMode}), + Parent = gen:get_parent(), + enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent). %%--------------------------------------------------------------------------- %% API helpers @@ -580,15 +575,13 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> case Result of {CallbackMode,State,Data} -> case callback_mode(CallbackMode) of - true - when CallbackMode =:= state_functions, is_atom(State); - CallbackMode =/= state_functions -> + true -> proc_lib:init_ack(Starter, {ok,self()}), enter( Module, Opts, CallbackMode, State, Data, ServerRef, [], Parent); false -> - Error = {bad_return_value,Result}, + Error = {callback_mode,CallbackMode}, proc_lib:init_ack(Starter, {error,Error}), exit(Error) end; @@ -600,7 +593,7 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> Module, Opts, CallbackMode, State, Data, ServerRef, Actions, Parent); false -> - Error = {bad_return_value,Result}, + Error = {callback_mode,CallbackMode}, proc_lib:init_ack(Starter, {error,Error}), exit(Error) end; @@ -638,11 +631,12 @@ system_code_change( Result -> Result end of - {ok,NewState,NewData} -> - {ok, - S#{ - state := NewState, - data := NewData}}; + {NewCallbackMode,NewState,NewData} -> + callback_mode(NewCallbackMode) orelse + error({callback_mode,NewCallbackMode}), + {ok,S#{state := NewState, data := NewData}}; + {ok,_} = Error -> + error({case_clause,Error}); Error -> Error end. @@ -825,6 +819,18 @@ loop_events( Result -> loop_event_result( Parent, Debug, S, Events, Event, Result); + error:badarg when CallbackMode =:= state_functions -> + case erlang:get_stacktrace() of + [{erlang,apply,[Module,State,_],_}|Stacktrace] -> + Args = [Type,Content,Data], + terminate( + error, + {undef_state_function,{Module,State,Args}}, + Stacktrace, + Debug, S, Q); + Stacktrace -> + terminate(error, badarg, Stacktrace, Debug, S, Q) + end; error:undef -> %% Process an undef to check for the simple mistake %% of calling a nonexistent state function @@ -861,7 +867,7 @@ loop_events( %% Interpret all callback return variants loop_event_result( Parent, Debug, - #{callback_mode := CallbackMode, state := State, data := Data} = S, + #{state := State, data := Data} = S, Events, Event, Result) -> case Result of stop -> @@ -887,15 +893,11 @@ loop_event_result( exit, Reason, ?STACKTRACE(), Debug, NewS, Q, Replies), %% Since we got back here Replies was bad terminate(Class, NewReason, Stacktrace, NewDebug, NewS, Q); - {next_state,NextState,NewData} - when CallbackMode =:= state_functions, is_atom(NextState); - CallbackMode =/= state_functions -> + {next_state,NextState,NewData} -> loop_event_actions( Parent, Debug, S, Events, Event, State, NextState, NewData, []); - {next_state,NextState,NewData,Actions} - when CallbackMode =:= state_functions, is_atom(NextState); - CallbackMode =/= state_functions -> + {next_state,NextState,NewData,Actions} -> loop_event_actions( Parent, Debug, S, Events, Event, State, NextState, NewData, Actions); diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 27fb7d9650..3deb5fd986 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -637,9 +637,9 @@ code_change(Config) -> {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), {idle,data} = sys:get_state(Pid), sys:suspend(Pid), - sys:change_code(Pid, ?MODULE, old_vsn, extra), + sys:change_code(Pid, ?MODULE, old_vsn, state_functions), sys:resume(Pid), - {idle,{old_vsn,data,extra}} = sys:get_state(Pid), + {idle,{old_vsn,data,state_functions}} = sys:get_state(Pid), stop_it(Pid). call_format_status(Config) -> @@ -1561,8 +1561,8 @@ wrap_result(Result) -> -code_change(OldVsn, State, Data, Extra) -> - {ok,State,{OldVsn,Data,Extra}}. +code_change(OldVsn, State, Data, CallbackMode) -> + {CallbackMode,State,{OldVsn,Data,CallbackMode}}. format_status(terminate, [_Pdict,State,Data]) -> {formatted,State,Data}; diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml index 72aaafd780..27b9b7c761 100644 --- a/system/doc/design_principles/statem.xml +++ b/system/doc/design_principles/statem.xml @@ -90,11 +90,11 @@ State(S) x Event(E) -> Actions(A), State(S')</pre> <marker id="callback_modes" /> <title>Callback Modes</title> <p> - The <c>gen_statem</c> behaviour supports two different - <seealso marker="stdlib:gen_statem#type-action"> - callback modes. + The <c>gen_statem</c> behaviour supports two different callback modes. + In the mode + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>state_functions</c>, </seealso> - In the mode <c>state_functions</c>, the state transition rules are written as a number of Erlang functions, which conform to the following convention: </p> @@ -103,7 +103,11 @@ StateName(EventType, EventContent, Data) -> .. code for actions here ... {next_state, NewStateName, NewData}.</pre> <p> - In the mode <c>handle_event_function</c> there is only one + In the mode + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>handle_event_function</c> + </seealso> + there is only one Erlang function that implements all state transition rules: </p> <pre> @@ -125,9 +129,7 @@ handle_event(EventType, EventContent, State, Data) -> <title>Choosing Callback Mode</title> <p> The two - <seealso marker="stdlib:gen_statem#type-action"> - callback modes - </seealso> + <seealso marker="#callback_modes">callback modes</seealso> gives different possibilities and restrictions, but one goal remains: you want to handle all possible combinations of @@ -186,7 +188,7 @@ handle_event(EventType, EventContent, State, Data) -> <title>Example</title> <p> This is an example starting off as equivalent to the the example in the - <seealso marker="fsm"><c>gen_fsm</c> behaviour </seealso> + <seealso marker="fsm"><c>gen_fsm</c> behaviour</seealso> description. In later chapters additions and tweaks are made using features in <c>gen_statem</c> that <c>gen_fsm</c> does not have. At the end of this section you can find the example again @@ -217,6 +219,7 @@ handle_event(EventType, EventContent, State, Data) -> -module(code_lock). -behaviour(gen_statem). -define(NAME, code_lock). +-define(CALLBACK_MODE, state_functions). -export([start_link/1]). -export([button/1]). @@ -233,7 +236,7 @@ button(Digit) -> init(Code) -> do_lock(), Data = #{code => Code, remaining => Code}, - {state_functions,locked,Data}. + {?CALLBACK_MODE,locked,Data}. locked( cast, {button,Digit}, @@ -264,7 +267,7 @@ terminate(_Reason, State, _Data) -> State =/= locked andalso do_lock(), ok. code_change(_Vsn, State, Data, _Extra) -> - {ok,State,Data}. + {?CALLBACK_MODE,State,Data}. ]]></code> <p>The code is explained in the next sections.</p> </section> @@ -340,16 +343,20 @@ start_link(Code) -> If name registration succeeds, the new <c>gen_statem</c> process calls the callback function <c>code_lock:init(Code)</c>. This function is expected to return <c>{CallbackMode,State,Data}</c>, - where <c>CallbackMode</c> selects callback module state function - mode, in this case <c>state_functions</c> that is each state + where + <seealso marker="#callback_modes"> + <c>CallbackMode</c> + </seealso> + selects callback module state function mode, in this case + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>state_functions</c> + </seealso> + through the macro <c>?CALLBACK_MODE</c> that is; each state has got its own handler function. <c>State</c> is the initial state of the <c>gen_statem</c>, in this case <c>locked</c>; assuming the door is locked to begin with. <c>Data</c> is the internal server data of the <c>gen_statem</c>. - Here the server data is a - <seealso marker="stdlib:maps"> - map - </seealso> + Here the server data is a <seealso marker="stdlib:maps">map</seealso> with the key <c>code</c> that stores the correct button sequence and the key <c>remaining</c> that stores the remaining correct button sequence @@ -359,7 +366,7 @@ start_link(Code) -> init(Code) -> do_lock(), Data = #{code => Code, remaining => Code}, - {state_functions,locked,Data}. + {?CALLBACK_MODE,locked,Data}. ]]></code> <p> <seealso marker="stdlib:gen_statem#start_link/3"> @@ -536,13 +543,12 @@ handle_event({call,From}, code_length, #{code := Code} = Data) -> </p> <code type="erl"><![CDATA[ ... --export([handle_event/4]). +-define(CALLBACK_MODE, state_functions). ... +-export([handle_event/4]). -init(Code) -> - Data = #{code => Code, remaining => Code}, - {handle_event_function,locked,Data}. +... handle_event(cast, {button,Digit}, State, #{code := Code} = Data) -> case State of @@ -598,8 +604,8 @@ handle_event(timeout, _, open, Data) -> <code type="erl"><![CDATA[ init(Args) -> process_flag(trap_exit, true), + do_lock(), ... - {CallbackMode,State,Data}. ]]></code> <p> In this example we let the <c>terminate/3</c> function @@ -994,11 +1000,18 @@ do_unlock() -> utilizing a helper function <c>enter/3</c> for state entry: </p> <code type="erl"><![CDATA[ +... +-define(CALLBACK_MODE, state_functions). + +... + init(Code) -> + process_flag(trap_exit, true), Data = #{code => Code}, - enter(state_functions, locked, Data). + enter(?CALLBACK_MODE, locked, Data). ... + locked(internal, enter, _Data) -> do_lock(), {keep_state,Data#{remaining => Code}}; @@ -1053,6 +1066,7 @@ enter(Tag, State, Data) -> -module(code_lock). -behaviour(gen_statem). -define(NAME, code_lock_2). +-define(CALLBACK_MODE, state_functions). -export([start_link/1,stop/0]). -export([button/1,code_length/0]). @@ -1070,8 +1084,9 @@ code_length() -> gen_statem:call(?NAME, code_length). init(Code) -> + process_flag(trap_exit, true), Data = #{code => Code}, - enter(state_functions, locked, Data). + enter(?CALLBACK_MODE, locked, Data). locked(internal, enter, #{code := Code} = Data) -> do_lock(), @@ -1115,7 +1130,7 @@ terminate(_Reason, State, _Data) -> State =/= locked andalso do_lock(), ok. code_change(_Vsn, State, Data, _Extra) -> - {ok,State,Data}. + {?CALLBACK_MODE,State,Data}. ]]></code> </section> @@ -1129,14 +1144,10 @@ code_change(_Vsn, State, Data, _Extra) -> </p> <code type="erl"><![CDATA[ ... --export([handle_event/4]). +-define(CALLBACK_MODE, handle_event_function). ... - -init(Code) -> - process_flag(trap_exit, true), - Data = #{code => Code}, - enter(handle_event_function, locked, Data). +-export([handle_event/4]). ... @@ -1185,11 +1196,10 @@ handle_event({call,From}, code_length, _State, #{code := Code}) -> <section> <title>Complex State</title> <p> - The - <seealso marker="stdlib:gen_statem#type-action"> - callback mode + The callback mode + <seealso marker="stdlib:gen_statem#type-callback_mode"> + <c>handle_event_function</c> </seealso> - <c>handle_event_function</c> enables using a non-atom state as described in <seealso marker="#callback_modes"> Callback Modes, @@ -1245,6 +1255,7 @@ handle_event({call,From}, code_length, _State, #{code := Code}) -> -module(code_lock). -behaviour(gen_statem). -define(NAME, code_lock_3). +-define(CALLBACK_MODE, handle_event_function). -export([start_link/2,stop/0]). -export([button/1,code_length/0,set_lock_button/1]). @@ -1267,7 +1278,7 @@ set_lock_button(LockButton) -> init({Code,LockButton}) -> process_flag(trap_exit, true), Data = #{code => Code}, - enter(handle_event_function, {locked,LockButton}, Data, []). + enter(?CALLBACK_MODE, {locked,LockButton}, Data, []). %% State: locked handle_event(internal, enter, {locked,_}, #{code := Code} = Data) -> @@ -1323,7 +1334,7 @@ terminate(_Reason, State, _Data) -> State =/= locked andalso do_lock(), ok. code_change(_Vsn, State, Data, _Extra) -> - {ok,State,Data}. + {?CALLBACK_MODE,State,Data}. ]]></code> <p> It may be an ill-fitting model for a physical code lock |