From 85e9fed232a6d89e3659cabbb2169cf3e21127e3 Mon Sep 17 00:00:00 2001
From: Raimo Niskanen <raimo@erlang.org>
Date: Tue, 24 Jan 2017 14:15:26 +0100
Subject: Implement repeat_state and repeat_state_and_data

---
 lib/stdlib/doc/src/gen_statem.xml       |  82 +++++++++++++++++++---
 lib/stdlib/src/gen_statem.erl           | 116 ++++++++++++++++++--------------
 lib/stdlib/test/gen_statem_SUITE.erl    |  35 +++++++---
 system/doc/design_principles/statem.xml |  11 +++
 4 files changed, 175 insertions(+), 69 deletions(-)

diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 6fa2d86e0b..b20abbea5d 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -587,8 +587,8 @@ handle_event(_, _, State, Data) ->
       <name name="state_enter"/>
       <desc>
 	<p>
-	  If the state machine should use <em>state enter calls</em>
-	  is selected when starting the  <c>gen_statem</c>
+	  Whether the state machine should use <em>state enter calls</em>
+	  or not is selected when starting the  <c>gen_statem</c>
 	  and after code change using the return value from
 	  <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>.
 	</p>
@@ -606,7 +606,16 @@ handle_event(_, _, State, Data) ->
 	  See
 	  <seealso marker="#Module:StateName/3"><c>Module:StateName/3</c></seealso>
 	  and
-	      <seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
+	  <seealso marker="#Module:handle_event/4"><c>Module:handle_event/4</c></seealso>.
+	  Such a call can be repeated by returning a
+	  <seealso marker="#type-state_callback_result">
+	    <c>repeat_state</c>
+	  </seealso>
+	  or
+	  <seealso marker="#type-state_callback_result">
+	    <c>repeat_state_and_data</c>
+	  </seealso>
+	  tuple from the state callback.
 	</p>
 	<p>
 	  If 
@@ -625,7 +634,8 @@ handle_event(_, _, State, Data) ->
 	  right before entering the initial state even though this
 	  formally is not a state change.
 	  In this case <c>OldState</c> will be the same as <c>State</c>,
-	  which can not happen for a subsequent state change.
+	  which can not happen for a subsequent state change,
+	  but will happen when repeating the state enter call.
 	</p>
       </desc>
     </datatype>
@@ -640,7 +650,15 @@ handle_event(_, _, State, Data) ->
 	<list type="ordered">
 	  <item>
             <p>
-	      If the state changes or is the initial state, and
+	      If the state changes, is the initial state,
+	      <seealso marker="#type-state_callback_result">
+		<c>repeat_state</c>
+	      </seealso>
+	      or
+	      <seealso marker="#type-state_callback_result">
+		<c>repeat_state_and_data</c>
+	      </seealso>
+	      is used, and also
 	      <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>
 	      are used, the <c>gen_statem</c> calls
 	      the new state callback with arguments
@@ -1095,6 +1113,37 @@ handle_event(_, _, State, Data) ->
 	      <c>{next_state,CurrentState,CurrentData,<anno>Actions</anno>}</c>.
 	    </p>
 	  </item>
+	  <tag><c>repeat_state</c></tag>
+	  <item>
+	    <p>
+	      The <c>gen_statem</c> keeps the current state, or
+	      does a state transition to the current state if you like,
+	      sets <c><anno>NewData</anno></c>,
+	      and executes all <c><anno>Actions</anno></c>.
+	      If the <c>gen_statem</c> runs with
+	      <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
+	      the state enter call is repeated, see type
+	      <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
+	      otherwise <c>repeat_state</c> is the same as
+	      <c>keep_state</c>.
+	    </p>
+	  </item>
+	  <tag><c>repeat_state_and_data</c></tag>
+	  <item>
+	    <p>
+	      The <c>gen_statem</c> keeps the current state and data, or
+	      does a state transition to the current state if you like,
+	      and executes all <c><anno>Actions</anno></c>.
+	      This is the same as
+	      <c>{repeat_state,CurrentData,<anno>Actions</anno>}</c>.
+	      If the <c>gen_statem</c> runs with
+	      <seealso marker="#type-state_enter"><em>state enter calls</em></seealso>,
+	      the state enter call is repeated, see type
+	      <seealso marker="#type-transition_option"><c>transition_option()</c></seealso>,
+	      otherwise <c>repeat_state_and_data</c> is the same as
+	      <c>keep_state_and_data</c>.
+	    </p>
+	  </item>
 	  <tag><c>stop</c></tag>
 	  <item>
 	    <p>
@@ -1870,22 +1919,33 @@ handle_event(_, _, State, Data) ->
 	  <seealso marker="#type-enter_action">actions</seealso>
 	  that may be returned:
 	  <seealso marker="#type-postpone"><c>postpone()</c></seealso>
-	  and 
+	  is not allowed since a <em>state enter call</em> is not
+	  an event so there is no event to postpone, and 
 	  <seealso marker="#type-action"><c>{next_event,_,_}</c></seealso>
-	  are not allowed.
+	  is not allowed since using <em>state enter calls</em>
+	  should not affect how events are consumed and produced.
 	  You may also not change states from this call.
 	  Should you return <c>{next_state,NextState, ...}</c>
 	  with <c>NextState =/= State</c> the <c>gen_statem</c> crashes.
-	  You are advised to use <c>{keep_state,...}</c> or
-	  <c>keep_state_and_data</c>.
+	  It is possible to use <c>{repeat_state, ...}</c>,
+	  <c>{repeat_state_and_data,_}</c> or
+	  <c>repeat_state_and_data</c> but all of them makes little
+	  sense since you immediately will be called again with a new
+	  <em>state enter call</em> making this just a weird way
+	  of looping, and there are better ways to loop in Erlang.
+	  You are advised to use <c>{keep_state,...}</c>,
+	  <c>{keep_state_and_data,_}</c> or
+	  <c>keep_state_and_data</c> since you can not change states
+	  from a <em>state enter call</em> anyway.
 	</p>
 	<p>
 	  Note the fact that you can use
 	  <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>
 	  to return the result, which can be useful.
 	  For example to bail out with <c>throw(keep_state_and_data)</c>
-	  from deep within complex code that is in no position to
-	  return <c>{next_state,State,Data}</c>.
+	  from deep within complex code that can not
+	  return <c>{next_state,State,Data}</c> because
+	  <c>State</c> or <c>Data</c> is no longer in scope.
 	</p>
       </desc>
     </func>
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 4b5f5f676c..5de31ebfe0 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -185,12 +185,23 @@
 	'keep_state_and_data' | % {keep_state_and_data,[]}
 	{'keep_state_and_data', % Keep state and data -> only actions
 	 Actions :: [ActionType] | ActionType} |
+	%%
+	{'repeat_state', % {repeat_state,NewData,[]}
+	 NewData :: data()} |
+	{'repeat_state', % Repeat state, change data
+	 NewData :: data(),
+	 Actions :: [ActionType] | ActionType} |
+	'repeat_state_and_data' | % {repeat_state_and_data,[]}
+	{'repeat_state_and_data', % Repeat state and data -> only actions
+	 Actions :: [ActionType] | ActionType} |
+	%%
 	'stop' | % {stop,normal}
 	{'stop', % Stop the server
 	 Reason :: term()} |
 	{'stop', % Stop the server
 	 Reason :: term(),
 	 NewData :: data()} |
+	%%
 	{'stop_and_reply', % Reply then stop the server
 	 Reason :: term(),
 	 Replies :: [reply_action()] | reply_action()} |
@@ -602,13 +613,10 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
       name => Name,
       state => State,
       data => Data,
-      postponed => P,
+      postponed => P
       %% The rest of the fields are set from to the arguments to
-      %% loop_event_actions/10 when it finally loops back to loop/3
+      %% loop_event_actions/11 when it finally loops back to loop/3
       %% in loop_events/10
-      %%
-      %% Marker for initial state, cleared immediately when used
-      init_state => true
      },
     NewDebug = sys_debug(Debug, S, State, {enter,Event,State}),
     case call_callback_mode(S) of
@@ -617,7 +625,7 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) ->
 	    TimerTypes = #{},
 	    loop_event_actions(
 	      Parent, NewDebug, NewS, TimerRefs, TimerTypes,
-	      Events, Event, State, Data, NewActions);
+	      Events, Event, State, Data, NewActions, true);
 	{Class,Reason,Stacktrace} ->
 	    terminate(
 	      Class, Reason, Stacktrace,
@@ -900,13 +908,13 @@ loop_event(
 	    {NewTimerRefs,NewTimerTypes} =
 		cancel_timer_by_type(
 		  timeout, TimerRefs, TimerTypes),
-	    {NewData,NextState,Actions} =
+	    {NewData,NextState,Actions,EnterCall} =
 		parse_event_result(
 		  true, Debug, NewS, Result,
 		  Events, Event, State, Data),
 	    loop_event_actions(
 	      Parent, Debug, S, NewTimerRefs, NewTimerTypes,
-	      Events, Event, NextState, NewData, Actions);
+	      Events, Event, NextState, NewData, Actions, EnterCall);
 	{Class,Reason,Stacktrace} ->
 	    terminate(
 	      Class, Reason, Stacktrace, Debug, S, [Event|Events])
@@ -915,31 +923,16 @@ loop_event(
 loop_event_actions(
   Parent, Debug,
   #{state := State, state_enter := StateEnter} = S, TimerRefs, TimerTypes,
-  Events, Event, NextState, NewData, Actions) ->
+  Events, Event, NextState, NewData,
+  Actions, EnterCall) ->
     case parse_actions(Debug, S, State, Actions) of
 	{ok,NewDebug,Hibernate,TimeoutsR,Postpone,NextEventsR} ->
 	    if
-		StateEnter, NextState =/= State ->
+		StateEnter, EnterCall ->
 		    loop_event_enter(
 		      Parent, NewDebug, S, TimerRefs, TimerTypes,
 		      Events, Event, NextState, NewData,
 		      Hibernate, TimeoutsR, Postpone, NextEventsR);
-		StateEnter ->
-		    case maps:is_key(init_state, S) of
-			true ->
-			    %% Avoid infinite loop in initial state
-			    %% with state entry events
-			    NewS = maps:remove(init_state, S),
-			    loop_event_enter(
-			      Parent, NewDebug, NewS, TimerRefs, TimerTypes,
-			      Events, Event, NextState, NewData,
-			      Hibernate, TimeoutsR, Postpone, NextEventsR);
-			false ->
-			    loop_event_result(
-			      Parent, NewDebug, S, TimerRefs, TimerTypes,
-			      Events, Event, NextState, NewData,
-			      Hibernate, TimeoutsR, Postpone, NextEventsR)
-		    end;
 		true ->
 		    loop_event_result(
 		      Parent, NewDebug, S, TimerRefs, TimerTypes,
@@ -958,14 +951,16 @@ loop_event_enter(
   Hibernate, TimeoutsR, Postpone, NextEventsR) ->
     case call_state_function(S, enter, State, NextState, NewData) of
 	{ok,Result,NewS} ->
-	    {NewerData,_,Actions} =
-		parse_event_result(
-		  false, Debug, NewS, Result,
-		  Events, Event, NextState, NewData),
-	    loop_event_enter_actions(
-	      Parent, Debug, NewS, TimerRefs, TimerTypes,
-	      Events, Event, NextState, NewerData,
-	      Hibernate, TimeoutsR, Postpone, NextEventsR, Actions);
+	    case parse_event_result(
+		   false, Debug, NewS, Result,
+		   Events, Event, NextState, NewData) of
+		{NewerData,_,Actions,EnterCall} ->
+		    loop_event_enter_actions(
+		      Parent, Debug, NewS, TimerRefs, TimerTypes,
+		      Events, Event, NextState, NewerData,
+		      Hibernate, TimeoutsR, Postpone, NextEventsR,
+		      Actions, EnterCall)
+	    end;
 	{Class,Reason,Stacktrace} ->
 	    terminate(
 	      Class, Reason, Stacktrace,
@@ -974,19 +969,27 @@ loop_event_enter(
     end.
 
 loop_event_enter_actions(
-  Parent, Debug, S, TimerRefs, TimerTypes,
+  Parent, Debug, #{state_enter := StateEnter} = S, TimerRefs, TimerTypes,
   Events, Event, NextState, NewData,
-  Hibernate, TimeoutsR, Postpone, NextEventsR, Actions) ->
+  Hibernate, TimeoutsR, Postpone, NextEventsR,
+  Actions, EnterCall) ->
     case
 	parse_enter_actions(
-	  Debug, S, NextState, Actions,
-	  Hibernate, TimeoutsR)
+	  Debug, S, NextState, Actions, Hibernate, TimeoutsR)
     of
 	{ok,NewDebug,NewHibernate,NewTimeoutsR,_,_} ->
-	    loop_event_result(
-	      Parent, NewDebug, S, TimerRefs, TimerTypes,
-	      Events, Event, NextState, NewData,
-	      NewHibernate, NewTimeoutsR, Postpone, NextEventsR);
+	    if
+		StateEnter, EnterCall ->
+		    loop_event_enter(
+		      Parent, NewDebug, S, TimerRefs, TimerTypes,
+		      Events, Event, NextState, NewData,
+		      NewHibernate, NewTimeoutsR, Postpone, NextEventsR);
+		true ->
+		    loop_event_result(
+		      Parent, NewDebug, S, TimerRefs, TimerTypes,
+		      Events, Event, NextState, NewData,
+		      NewHibernate, NewTimeoutsR, Postpone, NextEventsR)
+	    end;
 	{Class,Reason,Stacktrace} ->
 	    terminate(
 	      Class, Reason, Stacktrace,
@@ -1212,6 +1215,7 @@ parse_event_result(
 	    terminate(
 	      exit, Reason, ?STACKTRACE(),
 	      Debug, S#{data := NewData}, [Event|Events]);
+	%%
 	{stop_and_reply,Reason,Replies} ->
 	    Q = [Event|Events],
 	    reply_then_terminate(
@@ -1222,22 +1226,34 @@ parse_event_result(
 	    reply_then_terminate(
 	      exit, Reason, ?STACKTRACE(),
 	      Debug, S#{data := NewData}, Q, Replies);
+	%%
 	{next_state,State,NewData} ->
-	    {NewData,State,[]};
+	    {NewData,State,[],false};
 	{next_state,NextState,NewData} when AllowStateChange ->
-	    {NewData,NextState,[]};
+	    {NewData,NextState,[],true};
 	{next_state,State,NewData,Actions} ->
-	    {NewData,State,Actions};
+	    {NewData,State,Actions,false};
 	{next_state,NextState,NewData,Actions} when AllowStateChange ->
-	    {NewData,NextState,Actions};
+	    {NewData,NextState,Actions,true};
+	%%
 	{keep_state,NewData} ->
-	    {NewData,State,[]};
+	    {NewData,State,[],false};
 	{keep_state,NewData,Actions} ->
-	    {NewData,State,Actions};
+	    {NewData,State,Actions,false};
 	keep_state_and_data ->
-	    {Data,State,[]};
+	    {Data,State,[],false};
 	{keep_state_and_data,Actions} ->
-	    {Data,State,Actions};
+	    {Data,State,Actions,false};
+	%%
+	{repeat_state,NewData} ->
+	    {NewData,State,[],true};
+	{repeat_state,NewData,Actions} ->
+	    {NewData,State,Actions,true};
+	repeat_state_and_data ->
+	    {Data,State,[],true};
+	{repeat_state_and_data,Actions} ->
+	    {Data,State,Actions,true};
+	%%
 	_ ->
 	    terminate(
 	      error,
diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl
index 3eddaab316..24f0864b3c 100644
--- a/lib/stdlib/test/gen_statem_SUITE.erl
+++ b/lib/stdlib/test/gen_statem_SUITE.erl
@@ -600,15 +600,26 @@ state_enter(_Config) ->
 		  (internal, Prev, N) ->
 		      Self ! {internal,start,Prev,N},
 		      {keep_state,N + 1};
+		  ({call,From}, repeat, N) ->
+		      {repeat_state,N + 1,
+		       [{reply,From,{repeat,start,N}}]};
 		  ({call,From}, echo, N) ->
-		      {next_state,wait,N + 1,{reply,From,{echo,start,N}}};
+		      {next_state,wait,N + 1,
+		       {reply,From,{echo,start,N}}};
 		  ({call,From}, {stop,Reason}, N) ->
-		      {stop_and_reply,Reason,[{reply,From,{stop,N}}],N + 1}
+		      {stop_and_reply,Reason,
+		       [{reply,From,{stop,N}}],N + 1}
 	      end,
 	  wait =>
-	      fun (enter, Prev, N) ->
+	      fun (enter, Prev, N) when N < 5 ->
+		      {repeat_state,N + 1,
+		       {reply,{Self,N},{enter,Prev}}};
+		  (enter, Prev, N) ->
 		      Self ! {enter,wait,Prev,N},
 		      {keep_state,N + 1};
+		  ({call,From}, repeat, N) ->
+		      {repeat_state_and_data,
+		       [{reply,From,{repeat,wait,N}}]};
 		  ({call,From}, echo, N) ->
 		      {next_state,start,N + 1,
 		       [{next_event,internal,wait},
@@ -620,11 +631,15 @@ state_enter(_Config) ->
 
     [{enter,start,start,1}] = flush(),
     {echo,start,2} = gen_statem:call(STM, echo),
-    [{enter,wait,start,3}] = flush(),
-    {wait,[4|_]} = sys:get_state(STM),
-    {echo,wait,4} = gen_statem:call(STM, echo),
-    [{enter,start,wait,5},{internal,start,wait,6}] = flush(),
-    {stop,7} = gen_statem:call(STM, {stop,bye}),
+    [{3,{enter,start}},{4,{enter,start}},{enter,wait,start,5}] = flush(),
+    {wait,[6|_]} = sys:get_state(STM),
+    {repeat,wait,6} = gen_statem:call(STM, repeat),
+    [{enter,wait,wait,6}] = flush(),
+    {echo,wait,7} = gen_statem:call(STM, echo),
+    [{enter,start,wait,8},{internal,start,wait,9}] = flush(),
+    {repeat,start,10} = gen_statem:call(STM, repeat),
+    [{enter,start,start,11}] = flush(),
+    {stop,12} = gen_statem:call(STM, {stop,bye}),
     [{'EXIT',STM,bye}] = flush(),
 
     {noproc,_} =
@@ -1790,6 +1805,10 @@ handle_event(
 	    {keep_state,[NewData|Machine]};
 	{keep_state,NewData,Ops} ->
 	    {keep_state,[NewData|Machine],Ops};
+	{repeat_state,NewData} ->
+	    {repeat_state,[NewData|Machine]};
+	{repeat_state,NewData,Ops} ->
+	    {repeat_state,[NewData|Machine],Ops};
 	Other ->
 	    Other
     end;
diff --git a/system/doc/design_principles/statem.xml b/system/doc/design_principles/statem.xml
index f627145f9f..8e7f496d9e 100644
--- a/system/doc/design_principles/statem.xml
+++ b/system/doc/design_principles/statem.xml
@@ -1180,6 +1180,17 @@ open(state_timeout, lock, Data) ->
     {next_state, locked, Data};
 ...
     ]]></code>
+    <p>
+      You can repeat the state entry code by returning one of
+      <c>{repeat_state, ...}</c>, <c>{repeat_state_and_data,_}</c>
+      or <c>repeat_state_and_data</c> that otherwise behaves
+      exactly like their <c>keep_state</c> siblings.
+      See the type
+      <seealso marker="stdlib:gen_statem#type-state_callback_result">
+	<c>state_callback_result()</c>
+      </seealso>
+      in the reference manual.
+    </p>
   </section>
 
 <!-- =================================================================== -->
-- 
cgit v1.2.3