aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml61
-rw-r--r--lib/stdlib/src/gen_statem.erl46
-rw-r--r--lib/stdlib/test/gen_statem_SUITE.erl44
3 files changed, 102 insertions, 49 deletions
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 9d98763973..f66f399137 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -475,8 +475,9 @@ erlang:'!' -----> Module:StateName/5
list order. This matters for <c>next_event</c> where
the last one in the list will become the next event to present
to the state functions. Regarding the other operations
- it is only for <c>{remove_event,<anno>EventPredicate</anno>}</c>
- that the order may matter.
+ it is only for <c>remove_event</c> with
+ <c><anno>EventPredicate</anno></c>
+ and for <c>reply_operation()</c> that the order may matter.
</p>
<taglist>
<tag>
@@ -583,19 +584,6 @@ erlang:'!' -----> Module:StateName/5
</item>
<tag>
<c>
- {next_state,<anno>StateOps</anno>}
- </c>
- </tag>
- <item>The same as
- <c>
- {next_state,State,StateData,<anno>StateOps</anno>}
- </c>but keeps the old <c>State</c> and <c>StateData</c>.
- Believe it or not, but this one has actually
- been proven useful to <c>throw/1</c> from deep down
- in the state logic.
- </item>
- <tag>
- <c>
{next_state,<anno>NewState</anno>,<anno>NewStateData</anno>}
</c>
</tag>
@@ -614,8 +602,47 @@ erlang:'!' -----> Module:StateName/5
</tag>
<item>The gen_statem will do a state transition to
<c><anno>NewState</anno></c>
- (which may be the same as <c>State</c>)
- and execute all <c>StateOps</c>
+ (which may be the same as the current state)
+ and execute all <c><anno>StateOps</anno></c>
+ </item>
+ <tag>
+ <c>
+ {keep_state,<anno>NewStateData</anno>}}
+ </c>
+ </tag>
+ <item>The same as
+ <c>
+ {keep_state,<anno>NewStateData</anno>,[]}
+ </c>
+ </item>
+ <tag>
+ <c>
+ {keep_state,<anno>NewStateData</anno>,<anno>StateOps</anno>}
+ </c>
+ </tag>
+ <item>The gen_statem will keep the current state, or
+ do a state transition to the current state if you like,
+ and execute all <c><anno>StateOps</anno></c>
+ </item>
+ <tag>
+ <c>
+ {keep_state_and_data}
+ </c>
+ </tag>
+ <item>The same as
+ <c>
+ {keep_state_and_data,[]}
+ </c>
+ </item>
+ <tag>
+ <c>
+ {keep_state_and_data,<anno>StateOps</anno>}
+ </c>
+ </tag>
+ <item>The gen_statem will keep the current state, or
+ do a state transition to the current state if you like,
+ also keep the current state data,
+ and execute all <c><anno>StateOps</anno></c>
</item>
</taglist>
</desc>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index e03e22b087..3da4443f13 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -114,14 +114,20 @@
Reason :: term(),
Replies :: [reply_operation()] | reply_operation(),
NewStateData :: state_data()} |
- {'next_state', % {next_state,State,StateData,StateOps} % Actually useful
- StateOps :: [state_op()] | state_op()} |
{'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(),
+ StateOps :: [state_op()] | state_op()} |
+ {'keep_state', % {keep_state,NewStateData,[]}
+ NewStateData :: state_data()} |
+ {'keep_state',
+ NewStateData :: state_data(),
+ StateOps :: [state_op()] | state_op()} |
+ {'keep_state_and_data'} | % {keep_state_and_data,[]}
+ {'keep_state_and_data',
StateOps :: [state_op()] | state_op()}.
%% The state machine init function. It is called only once and
@@ -477,7 +483,7 @@ enter(Module, Options, State, StateData, Server, InitOps, Parent) ->
S#{callback_mode := CallbackMode},
[],
{event,undefined}, % Will be discarded by {postpone,false}
- State, StateData,
+ PrevState, State, StateData,
StateOps++[{postpone,false}]);
[Reason] ->
?TERMINATE(Reason, Debug, S, [])
@@ -749,7 +755,10 @@ loop_events(
end.
%% Interpret all callback return value variants
-loop_event_result(Parent, Debug, S, Events, Event, Result) ->
+loop_event_result(
+ Parent, Debug,
+ #{state := State, state_data := StateData} = S,
+ Events, Event, Result) ->
case Result of
{stop,Reason} ->
?TERMINATE(Reason, Debug, S, [Event|Events]);
@@ -775,28 +784,39 @@ loop_event_result(Parent, Debug, S, Events, Event, Result) ->
?TERMINATE(
{bad_return_value,{stop,Reason,BadReplies,NewStateData}},
Debug, NewS, Q);
- {next_state,StateOps} ->
- #{state := State, state_data := StateData} = S,
- loop_event_state_ops(
- Parent, Debug, S, Events, Event,
- State, StateData, StateOps);
{next_state,NewState,NewStateData} ->
loop_event_state_ops(
Parent, Debug, S, Events, Event,
- NewState, NewStateData, []);
+ State, NewState, NewStateData, []);
{next_state,NewState,NewStateData,StateOps}
when is_list(StateOps) ->
loop_event_state_ops(
Parent, Debug, S, Events, Event,
- NewState, NewStateData, StateOps);
+ State, NewState, NewStateData, StateOps);
+ {keep_state,NewStateData} ->
+ loop_event_state_ops(
+ Parent, Debug, S, Events, Event,
+ State, State, NewStateData, []);
+ {keep_state,NewStateData,StateOps} ->
+ loop_event_state_ops(
+ Parent, Debug, S, Events, Event,
+ State, State, NewStateData, StateOps);
+ {keep_state_and_data} ->
+ loop_event_state_ops(
+ Parent, Debug, S, Events, Event,
+ State, State, StateData, []);
+ {keep_state_and_data,StateOps} ->
+ loop_event_state_ops(
+ Parent, Debug, S, Events, Event,
+ State, State, StateData, StateOps);
_ ->
?TERMINATE(
{bad_return_value,Result}, Debug, S, [Event|Events])
end.
loop_event_state_ops(
- Parent, Debug0, #{state := State, postponed := P0} = S, Events, Event,
- NewState, NewStateData, StateOps) ->
+ Parent, Debug0, #{postponed := P0} = S, Events, Event,
+ State, NewState, NewStateData, StateOps) ->
case collect_state_options(StateOps) of
{Postpone,Hibernate,Timeout,Operations} ->
P1 = % Move current event to postponed if Postpone
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 6249ada027..92dc59e843 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -1158,10 +1158,10 @@ idle(cast, badreturn, _, _, _Data) ->
badreturn;
idle({call,_From}, badreturn, _, _, _Data) ->
badreturn;
-idle({call,From}, {delayed_answer,T}, _, _, _) ->
+idle({call,From}, {delayed_answer,T}, _, _, Data) ->
receive
after T ->
- throw({next_state,{reply,From,delayed}})
+ throw({keep_state,Data,{reply,From,delayed}})
end;
idle({call,From}, {timeout,Time}, _, State, _Data) ->
{next_state,timeout,{From,Time},
@@ -1171,7 +1171,7 @@ idle(Type, Content, PrevState, State, Data) ->
undefined ->
case Type of
{call,From} ->
- throw({next_state,[{reply,From,'eh?'}]});
+ throw({keep_state,Data,[{reply,From,'eh?'}]});
_ ->
throw(
{stop,{unexpected,State,PrevState,Type,Content}})
@@ -1209,7 +1209,7 @@ wfor_conf({call,From}, confirm, _, _, Data) ->
{next_state,connected,Data,
[{reply,From,yes}]};
wfor_conf(cast, {ping,_,_}, _, _, _) ->
- {next_state,[postpone]};
+ {keep_state_and_data,[postpone]};
wfor_conf(cast, confirm, _, _, Data) ->
{next_state,connected,Data};
wfor_conf(Type, Content, PrevState, State, Data) ->
@@ -1220,7 +1220,7 @@ wfor_conf(Type, Content, PrevState, State, Data) ->
{next_state,idle,Data,
[{reply,From,'eh?'}]};
_ ->
- throw({next_state,[]})
+ throw({keep_state_and_data})
end;
Result ->
Result
@@ -1341,26 +1341,32 @@ handle_common_events(_, _, _, _, _) ->
%% states. Note that the state from init/1 is not wrapped
%% so both atom and non-atom states are tested.
handle_event(Type, Event, PrevState, State, Data) ->
- PrevStateName = unwrap(PrevState),
- StateName = unwrap(State),
- case
- ?MODULE:StateName(
+ PrevStateName = unwrap_state(PrevState),
+ StateName = unwrap_state(State),
+ try ?MODULE:StateName(
Type, Event, PrevStateName, StateName, Data) of
- {next_state,NewState,NewStateData} ->
- {next_state,wrap(NewState),NewStateData};
- {next_state,NewState,NewStateData,StateOps} ->
- {next_state,wrap(NewState),NewStateData,StateOps};
- Other ->
- Other
+ Result ->
+ wrap_result(Result)
+ catch
+ throw:Result ->
+ erlang:raise(
+ throw, wrap_result(Result), erlang:get_stacktrace())
end.
-unwrap([State]) ->
+unwrap_state([State]) ->
State;
-unwrap(State) ->
+unwrap_state(State) ->
State.
-wrap(State) ->
- [State].
+wrap_result(Result) ->
+ case Result of
+ {next_state,NewState,NewStateData} ->
+ {next_state,[NewState],NewStateData};
+ {next_state,NewState,NewStateData,StateOps} ->
+ {next_state,[NewState],NewStateData,StateOps};
+ Other ->
+ Other
+ end.