aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaimo Niskanen <[email protected]>2016-04-21 15:58:52 +0200
committerRaimo Niskanen <[email protected]>2016-04-21 15:58:52 +0200
commit26b3c7d60d52d8a7be006b06d856bb0f7276e77a (patch)
tree82fe6796d1139babe79073d7c4e392d7d7a5dc90
parent2977fbc6b658b0d664f7d3b36ecf8ca9e897aaa3 (diff)
downloadotp-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.xml71
-rw-r--r--lib/stdlib/src/gen_statem.erl60
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl8
-rw-r--r--system/doc/design_principles/statem.xml87
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>&nbsp;&nbsp;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