aboutsummaryrefslogtreecommitdiffstats
path: root/lib
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 /lib
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.
Diffstat (limited to 'lib')
-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
3 files changed, 79 insertions, 60 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};