aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml75
-rw-r--r--lib/stdlib/src/gen_statem.erl103
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl148
3 files changed, 191 insertions, 135 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 83d1f1e9e6..f7ce0f61ae 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -398,23 +398,6 @@ erlang:'!' -----> Module:State/5
<desc>
<taglist>
<tag>
- <c>{reply,<anno>Client</anno>,<anno>Reply</anno>}</c>
- </tag>
- <item>Reply to a client that called
- <seealso marker="#call/2"><c>call/2</c></seealso>.
- <c><anno>Client</anno></c> must be the term from the
- <seealso marker="#type-event_type">
- <c>{call,<anno>Client</anno>}</c>
- </seealso> argument to the
- <seealso marker="#state_function">state function</seealso>.
- </item>
- <tag><c>{stop,<anno>Reason</anno>}</c></tag>
- <item>The gen_statem will call
- <seealso marker="#Module:terminate/3">
- <c>Module:terminate/3</c>
- </seealso> with <c><anno>Reason</anno></c> and terminate.
- </item>
- <tag>
<c>
{insert_event,<anno>EventType</anno>,<anno>EventContent</anno>}
</c>
@@ -471,6 +454,24 @@ erlang:'!' -----> Module:State/5
</taglist>
</desc>
</datatype>
+ <datatype>
+ <name name="reply_operation" />
+ <desc>
+ <taglist>
+ <tag>
+ <c>{reply,<anno>Client</anno>,<anno>Reply</anno>}</c>
+ </tag>
+ <item>Reply to a client that called
+ <seealso marker="#call/2"><c>call/2</c></seealso>.
+ <c><anno>Client</anno></c> must be the term from the
+ <seealso marker="#type-event_type">
+ <c>{call,<anno>Client</anno>}</c>
+ </seealso> argument to the
+ <seealso marker="#state_function">state function</seealso>.
+ </item>
+ </taglist>
+ </desc>
+ </datatype>
</datatypes>
<funcs>
@@ -867,22 +868,39 @@ erlang:'!' -----> Module:State/5
<seealso marker="#type-event_type">event_type()</seealso>
</v>
<v>EventContent = term()</v>
- <v>Result = {NewState,NewStateData,StateOps}</v>
- <v>&nbsp;&nbsp;| {NewState,NewStateData}</v>
- <d>&nbsp;&nbsp;The same as <c>{NewState,NewStateData,[]}</c></d>
- <v>&nbsp;&nbsp;| {NewStateData}</v>
- <d>&nbsp;&nbsp;The same as <c>{State,NewStateData,[retry]}</c></d>
- <v>&nbsp;&nbsp;| {}</v>
- <d>&nbsp;&nbsp;The same as <c>{State,StateData,[]}</c></d>
- <v>&nbsp;&nbsp;| StateOps</v>
- <d>&nbsp;&nbsp;The same as <c>{State,StateData,StateOps}</c></d>
-
+ <v>Result = {stop,Reason,NewStateData}</v>
+ <d>&nbsp;&nbsp;The same as <c>{stop,Reason,[],NewStateData}</c></d>
+ <v>&nbsp;&nbsp;| {stop,Reason,Reply,NewStateData}</v>
+ <d>&nbsp;&nbsp;The same as
+ <c>{stop,Reason,[Reply],NewStateData}</c>
+ </d>
+ <v>&nbsp;&nbsp;| {stop,Reason,Replies,NewStateData}</v>
+ <d>&nbsp;&nbsp;The gen_statem will first send all
+ <c>Replies</c> and then call
+ <seealso marker="#Module:terminate/3">
+ <c>Module:terminate/3</c>
+ </seealso> with <c>Reason</c> and terminate.
+ </d>
+ <v>&nbsp;&nbsp;| {next_state,NewState,NewStateData}</v>
+ <d>&nbsp;&nbsp;The same as
+ <c>{next_state,NewState,NewStateData,NewStateData,[]}</c>
+ </d>
+ <v>&nbsp;&nbsp;| {next_state,NewState,NewStateData,StateOps}</v>
+ <d>&nbsp;&nbsp;The gen_statem will do a state transition to
+ <c>NewState</c> (which may be the same as <c>State</c>)
+ and execute all <c>StateOps</c>
+ </d>
+ <v>Reason = term()</v>
<v>PrevState = State = NewState =
<seealso marker="#type-state">state()</seealso>
</v>
<v>StateData = NewStateData =
<seealso marker="#type-state_data">state_data()</seealso>
</v>
+ <v>Reply =
+ <seealso marker="#type-reply_operation">reply_operation()</seealso>
+ </v>
+ <v>Replies = [Reply]</v>
<v>StateOps =
[<seealso marker="#type-state_op">state_op()</seealso>]
</v>
@@ -898,7 +916,8 @@ erlang:'!' -----> Module:State/5
from this or from any other
<seealso marker="#state_function">state function</seealso>
by returning with <c>{reply,Client,Reply}</c> in
- <seealso marker="#type-state_op">StateOps</seealso>
+ <seealso marker="#type-state_op">StateOps</seealso>, in
+ <seealso marker="#type-reply_operation">Replies</seealso>
or by calling
<seealso marker="#reply/2">
<c>gen_statem:reply(Client, Reply)</c>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 9bb5ed013b..7c3cd8c2f3 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -74,14 +74,13 @@
{'retry', Retry :: boolean()} |
'hibernate' | % Hibernate the server instead of going into receive
{'hibernate', Hibernate :: boolean()} |
+ (Timeout :: timeout()) | % {timeout,Timeout}
{'timeout', % Generate a ('timeout', Msg, ...) event after Time
Time :: timeout(), Msg :: term()}.
-type state_operation() ::
%% These can occur multiple times and are executed in order
%% of appearence in the state_op() list
- {'reply', % Reply to a client
- Client :: client(), Reply :: term()} |
- {'stop', Reason :: term()} | % Stop the server
+ reply_operation() |
{'insert_event', % Insert event as the next to handle
EventType :: event_type(),
EventContent :: term()} |
@@ -95,6 +94,9 @@
MonitorRef :: reference()} |
{'unlink', % Unlink and clean up mess(ages)
Id :: pid() | port()}.
+-type reply_operation() ::
+ {'reply', % Reply to a client
+ Client :: client(), Reply :: term()}.
%% The state machine init function. It is called only once and
%% the server is not running until this function has returned
@@ -122,12 +124,20 @@
PrevState :: state(),
State :: state(), % Current state
StateData :: state_data()) ->
- [state_op()] | % {State,StateData,[state_op()]}
- {} | % {State,StateData,[]}
- {NewStateData :: state_data()} | % {State,NewStateData,[retry]}
- {NewState :: state(),
- NewStateData :: state_data()} | % {NewState,NewStateData,[]}
- {NewState :: state(), NewStateData :: state_data(), [state_op()]}.
+ {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()}.
%% Clean up before the server terminates.
-callback terminate(
@@ -679,35 +689,42 @@ loop_events(
terminate(Class, Reason, Stacktrace, Debug, S, Q)
end.
-%% Interprete all callback return value variants
-loop_event_result(
- Parent, Debug,
- #{state := State, state_data := StateData} = S,
- Events, Event, Result) ->
+%% Interpret all callback return value variants
+loop_event_result(Parent, Debug, S, Events, Event, Result) ->
case Result of
- {} -> % Ignore
- loop_event_state_ops(
- Parent, Debug, S, Events, Event,
- State, StateData, []);
- {NewStateData} -> % Retry
- loop_event_state_ops(
- Parent, Debug, S, Events, Event,
- State, NewStateData, [retry]);
- {NewState,NewStateData} -> % Consume
+ {stop,Reason,NewStateData} ->
+ terminate(
+ Reason, Debug,
+ S#{state_data := NewStateData},
+ [Event|Events]);
+ {stop,Reason,Reply,NewStateData} ->
+ NewS = S#{state_data := NewStateData},
+ Q = [Event|Events],
+ Replies =
+ if
+ is_list(Reply) ->
+ Reply;
+ true ->
+ [Reply]
+ end,
+ BadReplies =
+ reply_then_terminate(Reason, Debug, NewS, Q, Replies),
+ %% Since it returned Replies was bad
+ terminate(
+ {bad_return_value,{stop,Reason,BadReplies,NewStateData}},
+ Debug, NewS, Q);
+ {next_state,NewState,NewStateData} ->
loop_event_state_ops(
Parent, Debug, S, Events, Event,
NewState, NewStateData, []);
- {NewState,NewStateData,StateOps} when is_list(StateOps) ->
+ {next_state,NewState,NewStateData,StateOps}
+ when is_list(StateOps) ->
loop_event_state_ops(
Parent, Debug, S, Events, Event,
NewState, NewStateData, StateOps);
- StateOps when is_list(StateOps) -> % Stay in state
- loop_event_state_ops(
- Parent, Debug, S, Events, Event,
- State, StateData, StateOps);
- BadReturn ->
+ _ ->
terminate(
- {bad_return_value,BadReturn}, Debug, S, [Event|Events])
+ {bad_return_value,Result}, Debug, S, [Event|Events])
end.
loop_event_state_ops(
@@ -750,7 +767,8 @@ loop_event_state_ops(
%% Pretend the timeout has just been received
{undefined,Q3 ++ [{timeout,Msg}]};
{timeout,Time,Msg} ->
- {erlang:start_timer(Time, self(), Msg),Q3}
+ {erlang:start_timer(Time, self(), Msg),
+ Q3}
end,
loop_events(
Parent, NewDebug,
@@ -820,11 +838,8 @@ process_state_operations([], Debug, _S, Q, P) ->
process_state_operations([Operation|Operations] = OOs, Debug, S, Q, P) ->
case Operation of
{reply,{_To,_Tag}=Client,Reply} ->
- reply(Client, Reply),
- NewDebug = sys_debug(Debug, S, {out,Reply,Client}),
+ NewDebug = do_reply(Debug, S, Client, Reply),
process_state_operations(Operations, NewDebug, S, Q, P);
- {stop,Reason} ->
- [Reason,Debug];
{insert_event,Type,Content} ->
case event_type(Type) of
true ->
@@ -854,6 +869,22 @@ process_state_operations([Operation|Operations] = OOs, Debug, S, Q, P) ->
end
end.
+reply_then_terminate(Reason, Debug, S, Q, []) ->
+ terminate(Reason, Debug, S, Q);
+reply_then_terminate(Reason, Debug, S, Q, [R|Rs] = RRs) ->
+ case R of
+ {reply,{_To,_Tag}=Client,Reply} ->
+ NewDebug = do_reply(Debug, S, Client, Reply),
+ reply_then_terminate(Reason, NewDebug, S, Q, Rs);
+ _ ->
+ RRs % bad_return_value
+ end.
+
+do_reply(Debug, S, Client, Reply) ->
+ reply(Client, Reply),
+ sys_debug(Debug, S, {out,Reply,Client}).
+
+
%% Remove oldest matching event from the queue(s)
remove_event(RemoveFun, Q, P) ->
try
@@ -928,7 +959,7 @@ remove_fun({unlink,Id}) ->
end
catch
Class:Reason ->
- {Class,Reason,erlang:get_stacktrace()}
+ [Class,Reason,erlang:get_stacktrace()]
end;
remove_fun(_) ->
undefined.
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 342be32acb..51e08f5ec1 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1068,29 +1068,30 @@ terminate(_Reason, _State, _Data) ->
idle(cast, {connect,Pid}, _, _, Data) ->
Pid ! accept,
- {wfor_conf,Data};
+ {next_state,wfor_conf,Data};
idle({call,From}, connect, _, _, Data) ->
- {wfor_conf,Data,[{reply,From,accept}]};
+ {next_state,wfor_conf,Data,[{reply,From,accept}]};
idle(cast, badreturn, _, _, _Data) ->
badreturn;
idle({call,_From}, badreturn, _, _, _Data) ->
badreturn;
-idle({call,From}, {delayed_answer,T}, _, _, _Data) ->
+idle({call,From}, {delayed_answer,T}, _, State, Data) ->
receive
after T ->
- [{reply,From,delayed}]
+ {next_state,State,Data,
+ [{reply,From,delayed}]}
end;
idle({call,From}, {timeout,Time}, _, State, _Data) ->
- {timeout, {From,Time}, [{timeout,Time,State}]};
+ {next_state,timeout,{From,Time},
+ [{timeout,Time,State}]};
idle(Type, Content, PrevState, State, Data) ->
case handle_common_events(Type, Content, PrevState, State, Data) of
- [] ->
+ undefined ->
case Type of
{call,From} ->
- {State,Data,[{reply,From,'eh?'}]};
+ {next_state,State,Data,[{reply,From,'eh?'}]};
_ ->
- {State,Data,
- [{stop,{unexpected,State,PrevState,Type,Content}}]}
+ {stop,{unexpected,State,PrevState,Type,Content},Data}
end;
Result ->
Result
@@ -1100,146 +1101,151 @@ timeout(timeout, idle, idle, timeout, {From,Time}) ->
TRef2 = erlang:start_timer(Time, self(), ok),
TRefC1 = erlang:start_timer(Time, self(), cancel1),
TRefC2 = erlang:start_timer(Time, self(), cancel2),
- {timeout2,
- {From, Time, TRef2},
+ {next_state,timeout2,{From,Time,TRef2},
[{cancel_timer, TRefC1},
{insert_event,internal,{cancel_timer,TRefC2}}]};
-timeout(_, _, _, _, Data) ->
- {Data}.
+timeout(_, _, _, State, Data) ->
+ {next_state,State,Data}.
timeout2(
internal, {cancel_timer,TRefC2}, timeout, _, {From,Time,TRef2}) ->
Time4 = Time * 4,
receive after Time4 -> ok end,
- {timeout3,{From,TRef2},[{cancel_timer,TRefC2}]};
-timeout2(_, _, _, _, Data) ->
- {Data}.
+ {next_state,timeout3,{From,TRef2},
+ [{cancel_timer,TRefC2}]};
+timeout2(_, _, _, State, Data) ->
+ {next_state,State,Data}.
timeout3(info, {timeout,TRef2,Result}, _, _, {From,TRef2}) ->
- {idle,state,[{reply,From,Result}]};
-timeout3(_, _, _, _, Data) ->
- {Data}.
+ {next_state,idle,state,
+ [{reply,From,Result}]};
+timeout3(_, _, _, State, Data) ->
+ {next_state,State,Data}.
wfor_conf({call,From}, confirm, _, _, Data) ->
- {connected,Data,[{reply,From,yes}]};
+ {next_state,connected,Data,
+ [{reply,From,yes}]};
wfor_conf(cast, confirm, _, _, Data) ->
- {connected,Data};
+ {next_state,connected,Data};
wfor_conf(Type, Content, PrevState, State, Data) ->
case handle_common_events(Type, Content, PrevState, State, Data) of
- [] ->
+ undefined ->
case Type of
{call,From} ->
- {idle,Data,[{reply,From,'eh?'}]};
+ {next_state,idle,Data,
+ [{reply,From,'eh?'}]};
_ ->
- {Data}
+ {next_state,State,Data}
end;
Result ->
Result
end.
connected({call,From}, {msg,Ref}, _, State, Data) ->
- {State,Data,[{reply,From,{ack,Ref}}]};
-connected(cast, {msg,From,Ref}, _, _, _Data) ->
+ {next_state,State,Data,
+ [{reply,From,{ack,Ref}}]};
+connected(cast, {msg,From,Ref}, _, State, Data) ->
From ! {ack,Ref},
- {};
+ {next_state,State,Data};
connected({call,From}, disconnect, _, _, Data) ->
- {idle,Data,[{reply,From,yes}]};
+ {next_state,idle,Data,
+ [{reply,From,yes}]};
connected(cast, disconnect, _, _, Data) ->
- {idle,Data};
+ {next_state,idle,Data};
connected(Type, Content, PrevState, State, Data) ->
case handle_common_events(Type, Content, PrevState, State, Data) of
- [] ->
+ undefined ->
case Type of
{call,From} ->
- [{reply,From,'eh?'}];
+ {next_state,State,Data,
+ [{reply,From,'eh?'}]};
_ ->
- {Data}
+ {next_state,State,Data}
end;
Result ->
Result
end.
-state0({call,From}, stop, _, State, Data) ->
- {State,Data,
- [{reply,From,stopped},
- {stop,normal}]};
+state0({call,From}, stop, _, _, Data) ->
+ {stop,normal,[{reply,From,stopped}],Data};
state0(Type, Content, PrevState, State, Data) ->
case handle_common_events(Type, Content, PrevState, State, Data) of
- [] ->
- {Data};
+ undefined ->
+ {next_state,State,Data};
Result ->
Result
end.
-hiber_idle({call,From}, 'alive?', _, _, _) ->
- [{reply,From,'alive!'}];
+hiber_idle({call,From}, 'alive?', _, State, Data) ->
+ {next_state,State,Data,
+ [{reply,From,'alive!'}]};
hiber_idle({call,From}, hibernate_sync, _, _, Data) ->
- {hiber_wakeup,Data,
+ {next_state,hiber_wakeup,Data,
[{reply,From,hibernating},
hibernate]};
hiber_idle(info, hibernate_later, _, State, _) ->
Tref = erlang:start_timer(1000, self(), hibernate),
- {State,Tref};
+ {next_state,State,Tref};
hiber_idle(info, hibernate_now, _, State, Data) ->
- {State,Data,[hibernate]};
+ {next_state,State,Data,
+ [hibernate]};
hiber_idle(info, {timeout,Tref,hibernate}, _, State, Tref) ->
- {State,[],
+ {next_state,State,[],
[hibernate]};
hiber_idle(cast, hibernate_async, _, _, Data) ->
- {hiber_wakeup,Data,
+ {next_state,hiber_wakeup,Data,
[hibernate]};
hiber_idle(Type, Content, PrevState, State, Data) ->
case handle_common_events(Type, Content, PrevState, State, Data) of
- [] ->
- {Data};
+ undefined ->
+ {next_state,State,Data};
Result ->
Result
end.
hiber_wakeup({call,From}, wakeup_sync, _, _, Data) ->
- {hiber_idle,Data,[{reply,From,good_morning}]};
+ {next_state,hiber_idle,Data,
+ [{reply,From,good_morning}]};
hiber_wakeup({call,From}, snooze_sync, _, State, Data) ->
- {State,Data,
+ {next_state,State,Data,
[{reply,From,please_just_five_more},
hibernate]};
hiber_wakeup(cast, wakeup_async, _, _, Data) ->
- {hiber_idle,Data};
-hiber_wakeup(cast, snooze_async, _, _, _Data) ->
- [hibernate];
+ {next_state,hiber_idle,Data};
+hiber_wakeup(cast, snooze_async, _, State, Data) ->
+ {next_state,State,Data,
+ [hibernate]};
hiber_wakeup(Type, Content, PrevState, State, Data) ->
case handle_common_events(Type, Content, PrevState, State, Data) of
- [] ->
- {Data};
+ undefined ->
+ {next_state,State,Data};
Result ->
Result
end.
handle_common_events({call,From}, get, _, State, Data) ->
- [{reply,From,{state,State,Data}}];
+ {next_state,State,Data,
+ [{reply,From,{state,State,Data}}]};
handle_common_events(cast, {get,Pid}, _, State, Data) ->
Pid ! {state,State,Data},
- {};
-handle_common_events({call,From}, stop, _, _, _) ->
- [{reply,From,stopped},
- {stop,normal}];
-handle_common_events(cast, stop, _, State, Data) ->
- {State,Data,
- [{stop,normal}]};
-handle_common_events({call,From}, {stop,Reason}, _, State, Data) ->
- {State,Data,
- [{reply,From,stopped},
- {stop,Reason}]};
-handle_common_events(cast, {stop,Reason}, _, _, _) ->
- [{stop,Reason}];
+ {next_state,State,Data};
+handle_common_events({call,From}, stop, _, _, Data) ->
+ {stop,normal,[{reply,From,stopped}],Data};
+handle_common_events(cast, stop, _, _, Data) ->
+ {stop,normal,Data};
+handle_common_events({call,From}, {stop,Reason}, _, _, Data) ->
+ {stop,Reason,{reply,From,stopped},Data};
+handle_common_events(cast, {stop,Reason}, _, _, Data) ->
+ {stop,Reason,Data};
handle_common_events({call,From}, 'alive?', _, State, Data) ->
- {State,Data,
+ {next_state,State,Data,
[{reply,From,yes}]};
handle_common_events(cast, {'alive?',Pid}, _, State, Data) ->
Pid ! yes,
- {State,Data};
+ {next_state,State,Data};
handle_common_events(_, _, _, _, _) ->
- [].
+ undefined.
code_change(_OldVsn, State, StateData, _Extra) ->
{ok,State,StateData}.