aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib')
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml64
-rw-r--r--lib/stdlib/src/gen_statem.erl36
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl155
3 files changed, 136 insertions, 119 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 096be3025e..8934d912c6 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -62,7 +62,7 @@ gen_statem:stop -----> Module:terminate/2
gen_statem:call
gen_statem:cast
erlang:send
-erlang:'!' -----> Module:StateName/5
+erlang:'!' -----> Module:StateName/4
Module:handle_event/5
- -----> Module:terminate/3
@@ -84,18 +84,28 @@ erlang:'!' -----> Module:StateName/5
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>
- that the implementation selects during <c>gen_statem</c> init.
+ that the implementation specifies during <c>gen_statem</c> init.
</p>
<p>When
<seealso marker="#type-callback_mode">callback_mode</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/5">
- <c>Module:StateName/5</c>
+ <seealso marker="#Module:StateName/4">
+ <c>Module:StateName/4</c>
</seealso>.
This gathers all code for a specific state
in one function and hence dispatches on state first.
+ Note that in this mode the callback function
+ <seealso marker="#Module:code_change/4">
+ <c>Module:code_change/4</c>
+ </seealso> makes the state name <c>code_change</c>
+ unusable. Actually you might get away with using it
+ as a state name but you will have to find a way to separate
+ the two callback roles e.g by observing that you can force
+ argument 3 into having different types for the different roles.
+ This is a slippery slope, though. It is easier
+ to simply not use <c>code_change</c> as a state name.
</p>
<p>When
<seealso marker="#type-callback_mode">callback_mode</seealso>
@@ -110,13 +120,11 @@ erlang:'!' -----> Module:StateName/5
states so you do not accidentally postpone one event
forever creating an infinite busy loop.
</p>
- <p>Any state name or any state value (depending on
- <seealso marker="#type-callback_mode">callback_mode</seealso>)
- is permitted with a small gotcha regarding the state
- <c>undefined</c> that is used as the previous state when
- the first <c>gen_statem</c> state function is called.
- You might need to know about this faked state if you
- inspect the previous state argument in your state functions.
+ <p>There is a small gotcha in both callback modes regarding
+ the state <c>undefined</c> that is used as the previous state
+ when the first state function is called. So either do not
+ use <c>undefined</c> as a state, or figure out
+ the implications yourself.
</p>
<p>The <c>gen_statem</c> enqueues incoming events in order of arrival
and presents these to the
@@ -181,7 +189,7 @@ erlang:'!' -----> Module:StateName/5
for a long time. However use this feature with care
since hibernation implies at least two garbage collections
(when hibernating and shortly after waking up) and that is not
- something you'd want to do between each event on a busy server.
+ something you would want to do between each event on a busy server.
</p>
</description>
@@ -350,13 +358,20 @@ erlang:'!' -----> Module:StateName/5
<datatype>
<name name="callback_mode" />
<desc>
+ <p>
+ The <c>callback_mode</c> 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
+ <seealso marker="#enter_loop/5">enter_loop/5-7</seealso>.
+ </p>
<taglist>
<tag><c>state_functions</c></tag>
<item>The state has to be of type
<seealso marker="#type-state_name"><c>state_name()</c></seealso>
and one callback function per state that is
- <seealso marker="#Module:StateName/5">
- <c>Module:StateName/5</c>
+ <seealso marker="#Module:StateName/4">
+ <c>Module:StateName/4</c>
</seealso> is used. This is the default.
</item>
<tag><c>handle_event_function</c></tag>
@@ -384,7 +399,7 @@ erlang:'!' -----> Module:StateName/5
<seealso marker="#state_function">state function</seealso>,
from <seealso marker="#Module:init/1">Module:init/1</seealso>
or given to
- <seealso marker="#enter_loop/5">enter_loop/5,6</seealso>.
+ <seealso marker="#enter_loop/6">enter_loop/6,7</seealso>.
</p>
<p>The processing order for a state change is:</p>
<list type="ordered">
@@ -990,7 +1005,7 @@ erlang:'!' -----> Module:StateName/5
<func>
<name>Module:StateName(EventType, EventContent,
- PrevStateName, StateName, Data) -> Result
+ PrevStateName, Data) -> Result
</name>
<name>Module:handle_event(EventType, EventContent,
PrevState, State, Data) -> Result
@@ -1004,9 +1019,6 @@ erlang:'!' -----> Module:StateName/5
<v>PrevStateName =
<seealso marker="#type-state_name">state_name()</seealso>
</v>
- <v>StateName =
- <seealso marker="#type-state_name">state_name()</seealso>
- </v>
<v>PrevState = State =
<seealso marker="#type-state">state()</seealso>
</v>
@@ -1023,10 +1035,9 @@ erlang:'!' -----> Module:StateName/5
<p>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 this function is called.
- If
- <seealso marker="#type-callback_mode">callback_mode</seealso>
- is <c>state_functions</c> then <c>Module:StateName/5</c> is called,
+ as a normal process message one of these functions is called.
+ If <seealso marker="#type-callback_mode">callback_mode</seealso>
+ is <c>state_functions</c> then <c>Module:StateName/4</c> is called,
and if it is <c>handle_event_function</c>
then <c>Module:handle_event/5</c> is called.
</p>
@@ -1043,13 +1054,10 @@ erlang:'!' -----> Module:StateName/5
<c>reply(Caller, Reply)</c>
</seealso>.
</p>
- <p><c>StateName</c> is useful in some odd cases for example
- if you call a common event handling function from your state
- function then you might want to pass <c>StateName</c>.
- </p>
<p><c>PrevStateName</c> and <c>PrevState</c> are useful
in some odd cases for example when you want to do something
- only at the first event in a state.
+ only at the first event in a state, or when you need to
+ handle events differently depending on the previous state.
Note that when <c>gen_statem</c> enters its first state
this is set to <c>undefined</c>.
</p>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 02c8d60c61..fe84a428f6 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -152,8 +152,7 @@
-callback state_name(
event_type(),
EventContent :: term(),
- PrevStateName :: state_name() | reference(),
- StateName :: state_name(), % Current state
+ PrevStateName :: state_name(), % Previous state name
Data :: data()) ->
state_callback_result().
%%
@@ -164,7 +163,7 @@
-callback handle_event(
event_type(),
EventContent :: term(),
- PrevState :: state(),
+ PrevState :: state(), % Previous state
State :: state(), % Current state
Data :: data()) ->
state_callback_result().
@@ -203,7 +202,7 @@
[init/1, % One may use enter_loop/5,6,7 instead
format_status/2, % Has got a default implementation
%%
- state_name/5, % Example for callback_mode =:= state_functions:
+ state_name/4, % 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
@@ -740,14 +739,13 @@ loop_events(
[{Type,Content} = Event|Events] = Q, Timer) ->
_ = (Timer =/= undefined) andalso
cancel_timer(Timer),
- Func =
+ try
case CallbackMode of
- handle_event_function ->
- handle_event;
state_functions ->
- State
- end,
- try Module:Func(Type, Content, PrevState, State, Data) of
+ Module:State(Type, Content, PrevState, Data);
+ handle_event_function ->
+ Module:handle_event(Type, Content, PrevState, State, Data)
+ end of
Result ->
loop_event_result(
Parent, Debug, S, Events, Event, Result)
@@ -759,15 +757,27 @@ loop_events(
%% Process an undef to check for the simple mistake
%% of calling a nonexistent state function
case erlang:get_stacktrace() of
- [{Module,Func,
- [Type,Content,PrevState,State,Data]=Args,
+ [{Module,State,
+ [Type,Content,PrevState,Data]=Args,
_}
- |Stacktrace] ->
+ |Stacktrace]
+ when CallbackMode =:= state_functions ->
terminate(
error,
{undef_state_function,{Module,State,Args}},
Stacktrace,
Debug, S, Q);
+ [{Module,handle_event,
+ [Type,Content,PrevState,State,Data]=Args,
+ _}
+ |Stacktrace]
+ when CallbackMode =:= handle_event_function ->
+ terminate(
+ error,
+ {undef_state_function,
+ {Module,handle_event,Args}},
+ Stacktrace,
+ Debug, S, Q);
Stacktrace ->
terminate(error, undef, Stacktrace, Debug, S, Q)
end;
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 81182eff71..3302171329 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1160,73 +1160,73 @@ terminate(_Reason, _State, _Data) ->
%% State functions
-idle(cast, {connect,Pid}, _, _, Data) ->
+idle(cast, {connect,Pid}, _, Data) ->
Pid ! accept,
{next_state,wfor_conf,Data};
-idle({call,From}, connect, _, _, Data) ->
+idle({call,From}, connect, _, Data) ->
gen_statem:reply(From, accept),
{next_state,wfor_conf,Data};
-idle(cast, badreturn, _, _, _Data) ->
+idle(cast, badreturn, _, _Data) ->
badreturn;
-idle({call,_From}, badreturn, _, _, _Data) ->
+idle({call,_From}, badreturn, _, _Data) ->
badreturn;
-idle({call,From}, {delayed_answer,T}, _, _, Data) ->
+idle({call,From}, {delayed_answer,T}, _, Data) ->
receive
after T ->
gen_statem:reply({reply,From,delayed}),
throw({keep_state,Data})
end;
-idle({call,From}, {timeout,Time}, _, State, _Data) ->
+idle({call,From}, {timeout,Time}, _, _Data) ->
{next_state,timeout,{From,Time},
- [{timeout,Time,State}]};
-idle(Type, Content, PrevState, State, Data) ->
- case handle_common_events(Type, Content, PrevState, State, Data) of
+ [{timeout,Time,idle}]};
+idle(Type, Content, PrevState, Data) ->
+ case handle_common_events(Type, Content, idle, Data) of
undefined ->
case Type of
{call,From} ->
throw({keep_state,Data,[{reply,From,'eh?'}]});
_ ->
throw(
- {stop,{unexpected,State,PrevState,Type,Content}})
+ {stop,{unexpected,idle,Type,Content,PrevState}})
end;
Result ->
Result
end.
-timeout(timeout, idle, idle, timeout, {From,Time}) ->
+timeout(timeout, idle, idle, {From,Time}) ->
TRef2 = erlang:start_timer(Time, self(), ok),
TRefC1 = erlang:start_timer(Time, self(), cancel1),
TRefC2 = erlang:start_timer(Time, self(), cancel2),
{next_state,timeout2,{From,Time,TRef2},
[{cancel_timer, TRefC1},
{next_event,internal,{cancel_timer,TRefC2}}]};
-timeout(_, _, _, State, Data) ->
- {next_state,State,Data}.
+timeout(_, _, _, Data) ->
+ {keep_state,Data}.
timeout2(
- internal, {cancel_timer,TRefC2}, timeout, _, {From,Time,TRef2}) ->
+ internal, {cancel_timer,TRefC2}, timeout, {From,Time,TRef2}) ->
Time4 = Time * 4,
receive after Time4 -> ok end,
{next_state,timeout3,{From,TRef2},
[{cancel_timer,TRefC2}]};
-timeout2(_, _, _, State, Data) ->
- {next_state,State,Data}.
+timeout2(_, _, _, Data) ->
+ {keep_state,Data}.
-timeout3(info, {timeout,TRef2,Result}, _, _, {From,TRef2}) ->
+timeout3(info, {timeout,TRef2,Result}, _, {From,TRef2}) ->
gen_statem:reply([{reply,From,Result}]),
{next_state,idle,state};
-timeout3(_, _, _, State, Data) ->
- {next_state,State,Data}.
+timeout3(_, _, _, Data) ->
+ {keep_state,Data}.
-wfor_conf({call,From}, confirm, _, _, Data) ->
+wfor_conf({call,From}, confirm, _, Data) ->
{next_state,connected,Data,
[{reply,From,yes}]};
-wfor_conf(cast, {ping,_,_}, _, _, _) ->
+wfor_conf(cast, {ping,_,_}, _, _) ->
{keep_state_and_data,[postpone]};
-wfor_conf(cast, confirm, _, _, Data) ->
+wfor_conf(cast, confirm, _, Data) ->
{next_state,connected,Data};
-wfor_conf(Type, Content, PrevState, State, Data) ->
- case handle_common_events(Type, Content, PrevState, State, Data) of
+wfor_conf(Type, Content, _, Data) ->
+ case handle_common_events(Type, Content, wfor_conf, Data) of
undefined ->
case Type of
{call,From} ->
@@ -1239,113 +1239,113 @@ wfor_conf(Type, Content, PrevState, State, Data) ->
Result
end.
-connected({call,From}, {msg,Ref}, _, State, Data) ->
- {next_state,State,Data,
+connected({call,From}, {msg,Ref}, _, Data) ->
+ {keep_state,Data,
[{reply,From,{ack,Ref}}]};
-connected(cast, {msg,From,Ref}, _, State, Data) ->
+connected(cast, {msg,From,Ref}, _, Data) ->
From ! {ack,Ref},
- {next_state,State,Data};
-connected({call,From}, disconnect, _, _, Data) ->
+ {keep_state,Data};
+connected({call,From}, disconnect, _, Data) ->
{next_state,idle,Data,
[{reply,From,yes}]};
-connected(cast, disconnect, _, _, Data) ->
+connected(cast, disconnect, _, Data) ->
{next_state,idle,Data};
-connected(cast, {ping,Pid,Tag}, _, State, Data) ->
+connected(cast, {ping,Pid,Tag}, _, Data) ->
Pid ! {pong,Tag},
- {next_state,State,Data};
-connected(Type, Content, PrevState, State, Data) ->
- case handle_common_events(Type, Content, PrevState, State, Data) of
+ {keep_state,Data};
+connected(Type, Content, _, Data) ->
+ case handle_common_events(Type, Content, connected, Data) of
undefined ->
case Type of
{call,From} ->
- {next_state,State,Data,
+ {keep_state,Data,
[{reply,From,'eh?'}]};
_ ->
- {next_state,State,Data}
+ {keep_state,Data}
end;
Result ->
Result
end.
-state0({call,From}, stop, _, _, Data) ->
+state0({call,From}, stop, _, Data) ->
{stop_and_reply,normal,[{reply,From,stopped}],Data};
-state0(Type, Content, PrevState, State, Data) ->
- case handle_common_events(Type, Content, PrevState, State, Data) of
+state0(Type, Content, _, Data) ->
+ case handle_common_events(Type, Content, state0, Data) of
undefined ->
- {next_state,State,Data};
+ {keep_state,Data};
Result ->
Result
end.
-hiber_idle({call,From}, 'alive?', _, State, Data) ->
- {next_state,State,Data,
+hiber_idle({call,From}, 'alive?', _, Data) ->
+ {keep_state,Data,
[{reply,From,'alive!'}]};
-hiber_idle({call,From}, hibernate_sync, _, _, Data) ->
+hiber_idle({call,From}, hibernate_sync, _, Data) ->
{next_state,hiber_wakeup,Data,
[{reply,From,hibernating},
hibernate]};
-hiber_idle(info, hibernate_later, _, State, _) ->
+hiber_idle(info, hibernate_later, _, _) ->
Tref = erlang:start_timer(1000, self(), hibernate),
- {next_state,State,Tref};
-hiber_idle(info, hibernate_now, _, State, Data) ->
- {next_state,State,Data,
+ {keep_state,Tref};
+hiber_idle(info, hibernate_now, _, Data) ->
+ {keep_state,Data,
[hibernate]};
-hiber_idle(info, {timeout,Tref,hibernate}, _, State, Tref) ->
- {next_state,State,[],
+hiber_idle(info, {timeout,Tref,hibernate}, _, Tref) ->
+ {keep_state,[],
[hibernate]};
-hiber_idle(cast, hibernate_async, _, _, Data) ->
+hiber_idle(cast, hibernate_async, _, Data) ->
{next_state,hiber_wakeup,Data,
[hibernate]};
-hiber_idle(Type, Content, PrevState, State, Data) ->
- case handle_common_events(Type, Content, PrevState, State, Data) of
+hiber_idle(Type, Content, _, Data) ->
+ case handle_common_events(Type, Content, hiber_idle, Data) of
undefined ->
- {next_state,State,Data};
+ {keep_state,Data};
Result ->
Result
end.
-hiber_wakeup({call,From}, wakeup_sync, _, _, Data) ->
+hiber_wakeup({call,From}, wakeup_sync, _, Data) ->
{next_state,hiber_idle,Data,
[{reply,From,good_morning}]};
-hiber_wakeup({call,From}, snooze_sync, _, State, Data) ->
- {next_state,State,Data,
+hiber_wakeup({call,From}, snooze_sync, _, Data) ->
+ {keep_state,Data,
[{reply,From,please_just_five_more},
hibernate]};
-hiber_wakeup(cast, wakeup_async, _, _, Data) ->
+hiber_wakeup(cast, wakeup_async, _, Data) ->
{next_state,hiber_idle,Data};
-hiber_wakeup(cast, snooze_async, _, State, Data) ->
- {next_state,State,Data,
+hiber_wakeup(cast, snooze_async, _, Data) ->
+ {keep_state,Data,
[hibernate]};
-hiber_wakeup(Type, Content, PrevState, State, Data) ->
- case handle_common_events(Type, Content, PrevState, State, Data) of
+hiber_wakeup(Type, Content, _, Data) ->
+ case handle_common_events(Type, Content, hiber_wakeup, Data) of
undefined ->
- {next_state,State,Data};
+ {keep_state,Data};
Result ->
Result
end.
-handle_common_events({call,From}, get, _, State, Data) ->
- {next_state,State,Data,
+handle_common_events({call,From}, get, State, Data) ->
+ {keep_state,Data,
[{reply,From,{state,State,Data}}]};
-handle_common_events(cast, {get,Pid}, _, State, Data) ->
+handle_common_events(cast, {get,Pid}, State, Data) ->
Pid ! {state,State,Data},
- {next_state,State,Data};
-handle_common_events({call,From}, stop, _, _, Data) ->
+ {keep_state,Data};
+handle_common_events({call,From}, stop, _, Data) ->
{stop_and_reply,normal,[{reply,From,stopped}],Data};
-handle_common_events(cast, stop, _, _, _) ->
+handle_common_events(cast, stop, _, _) ->
{stop,normal};
-handle_common_events({call,From}, {stop,Reason}, _, _, Data) ->
+handle_common_events({call,From}, {stop,Reason}, _, Data) ->
{stop_and_reply,Reason,{reply,From,stopped},Data};
-handle_common_events(cast, {stop,Reason}, _, _, _) ->
- {stop,Reason};
-handle_common_events({call,From}, 'alive?', _, State, Data) ->
- {next_state,State,Data,
+handle_common_events(cast, {stop,Reason}, _, _) ->
+ {stop,Reason};
+handle_common_events({call,From}, 'alive?', _, Data) ->
+ {keep_state,Data,
[{reply,From,yes}]};
-handle_common_events(cast, {'alive?',Pid}, _, State, Data) ->
+handle_common_events(cast, {'alive?',Pid}, _, Data) ->
Pid ! yes,
- {next_state,State,Data};
-handle_common_events(_, _, _, _, _) ->
+ {keep_state,Data};
+handle_common_events(_, _, _, _) ->
undefined.
%% Dispatcher to test callback_mode handle_event_function
@@ -1356,8 +1356,7 @@ handle_common_events(_, _, _, _, _) ->
handle_event(Type, Event, PrevState, State, Data) ->
PrevStateName = unwrap_state(PrevState),
StateName = unwrap_state(State),
- try ?MODULE:StateName(
- Type, Event, PrevStateName, StateName, Data) of
+ try ?MODULE:StateName(Type, Event, PrevStateName, Data) of
Result ->
wrap_result(Result)
catch