[erlang-questions] gen_statem: next event internal and reply

Raimo Niskanen raimo+erlang-questions@REDACTED
Wed Jul 31 14:36:21 CEST 2019


I have checked in a branch in the daily builds that clarifies
the documentation for transition_option() to be more explicit
about when replies are sent, and some other stuff.

/ Raimo


On Wed, Jul 31, 2019 at 12:00:14PM +0200, Raimo Niskanen wrote:
> On Wed, Jul 31, 2019 at 09:41:07AM +0100, Peter Morgan wrote:
> > Hi,
> > 
> > I regularly use the following pattern in gen_statem:
> > 
> > handle_event({call, From}, a, _, _) ->
> >   {keep_state_and_data, [{next_event, internal, work}, {reply, From, ok}]};
> > 
> > Where `work` is some side effect that needs to be done, and may be used by several other actions (hence wrapping it in a next_event) but the result is unimportant and doesn’t affect the reply. I use  next_event rather than calling work() directly because it can be traced via sys.
> > 
> > My assumption was that work would be processed before the reply was sent to the caller - i.e., that the actions were processed in order.
> > 
> > Instead, it appears that the reply action is processed immediately?
> > 
> > To work around this I have started using:
> > 
> > handle_event({call, From}, a, _, _) ->
> >   {keep_state_and_data, [{next_event, internal, work}, {next_event, internal, {reply, From, ok}]}};
> > handle_event(internal, {reply, From, Result}, _, _) ->
> >     {keep_state_and_data, {reply, From, Result}}.
> > 
> > The above ensures that work is completed before the reply is sent (result of the work is a side-effect here) - but is a rather ugly solution.
> > 
> > Is the above intended behaviour? In some cases the `work` event causes state changes, or queueing subsequent other work (via next_event, internal) that was expected to be completed before the {reply, From, ok} was consumed.
> 
> This is by design, intentional and documented (somewhat convoluted) in:
>   http://erlang.org/doc/man/gen_statem.html#type-transition_option
>   http://erlang.org/doc/man/gen_statem.html#type-action
>   http://erlang.org/doc/man/gen_statem.html#type-enter_action
>   http://erlang.org/doc/man/gen_statem.html#type-reply_action
> 
> The {next_event, Type, Content} transition action inserts an event
> to be handled during later event handler invocations.
> 
> {reply, From, Reply} immediately sends a reply during the state transition
> hence before the next event handler invocation.
> 
> Most of the transition actions set options for the state transition, and
> how these options affect the state transition is described in the first
> link above:
>   http://erlang.org/doc/man/gen_statem.html#type-transition_option
> 
> The actions are processed in order, but that is far from the whole truth.
> The only transition action that immediately does something for which the
> order matters is {reply,,}, the others just set transition options.
> Order also matters between {next_event,,} actions since their inserted
> events will be handled in order.
> 
> Your assumption that a {next_event,,,} action should be processed before a
> {reply,,} action would also be very confusing: if handling of the
> inserted event also would use {reply,,}; what would the order of the
> replies be?  Especially if both event handler invocations would insert
> multiple events and generate multiple replies...
> Then add state enter calls to that mix.
> 
> I think your solution is logical and only moderatly ugly.  Would it be
> possible to instead have the handler of (internal, work) decide on sending
> the reply based on some other state data instead of having to generate
> two internal events?
> 
> Best Regards
> / Raimo Niskanen
> 
> 
> > 
> > The above ordering of actions assumption caught me out recently, the early reply was causing a process to be killed before it had actually completed its work causing an unclean shutdown in a system.
> > 
> > BTW the additional of log is very useful in failure analysis - what did this statem recently do? :) thanks!
> > 
> > 
> > Thanks,
> > Peter.
> > 
> > Full example:
> > 
> > -module(exaple_statem_reply).
> > 
> > -behaviour(gen_statem).
> > -export([a/0]).
> > -export([b/0]).
> > -export([callback_mode/0]).
> > -export([handle_event/4]).
> > -export([init/1]).
> > -export([start/0]).
> > -export([stop/1]).
> > -include_lib("kernel/include/logger.hrl").
> > 
> > start() ->
> >     gen_statem:start({local, ?MODULE},
> >                      ?MODULE,
> >                     [],
> >                     [{debug, [log, trace]}]).
> > 
> > a() ->
> >     gen_statem:call(?MODULE, ?FUNCTION_NAME).
> > 
> > b() ->
> >     gen_statem:call(?MODULE, ?FUNCTION_NAME).
> > 
> > stop(S) ->
> >     gen_statem:stop(S).
> > 
> > init([]) ->
> >     {ok, limbo, #{}}.
> > 
> > 
> > callback_mode() ->
> >     handle_event_function.
> > 
> > handle_event({call, From}, a, _, _) ->
> >     {keep_state_and_data, [nei(work), {reply, From, ok}]};
> > 
> > handle_event({call, From}, b, _, _) ->
> >     {keep_state_and_data, [nei(work), nei({reply, From, ok})]};
> > 
> > handle_event(internal, work, _, _) ->
> >     keep_state_and_data;
> > 
> > handle_event(internal, {reply, From, Result}, _, _) ->
> >     {keep_state_and_data, {reply, From, Result}}.
> > 
> > nei(Event) ->
> >     {next_event, internal, Event}.
> > 
> > 
> > 1>   exaple_statem_reply:start().
> > *DBG* exaple_statem_reply enter in state limbo
> > *DBG* exaple_statem_reply consume internal init_state in state limbo
> > {ok,<0.161.0>}
> > 2>   exaple_statem_reply:a().
> > *DBG* exaple_statem_reply receive call a from <0.78.0> in state limbo
> > *DBG* exaple_statem_reply receive internal work in state limbo
> > *DBG* exaple_statem_reply send ok to <0.78.0>
> > *DBG* exaple_statem_reply consume call a from <0.78.0> in state limbo
> > ok
> > *DBG* exaple_statem_reply consume internal work in state limbo
> > 3>   sys:get_status(exaple_statem_reply).
> > {status,<0.161.0>,
> >         {module,gen_statem},
> >         [[{'$ancestors',[<0.78.0>]},
> >           {'$initial_call',{exaple_statem_reply,init,1}}],
> >          running,<0.161.0>,
> >          [{trace,true},
> >           {log,[3,
> >                 {{consume,{internal,work},limbo,limbo},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>},
> >                 {{consume,{{call,{<0.78.0>,
> >                                   #Ref<0.2400559651.2919759873.28633>}},
> >                            a},
> >                           limbo,limbo},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>},
> >                 {{out,ok,{<0.78.0>,#Ref<0.2400559651.2919759873.28633>}},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>},
> >                 {{in,{internal,work},limbo},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>},
> >                 {{in,{{call,{<0.78.0>,#Ref<0.2400559651.2919759873.28633>}},
> >                       a},
> >                      limbo},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>},
> >                 {{consume,{internal,init_state},limbo,limbo},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>},
> >                 {{enter,limbo},
> >                  exaple_statem_reply,#Fun<gen_statem.1.50707408>}]}],
> >          [{header,"Status for state machine exaple_statem_reply"},
> >           {data,[{"Status",running},
> >                  {"Parent",<0.161.0>},
> >                  {"Logged Events",
> >                   [{enter,limbo},
> >                    {consume,{internal,init_state},limbo,limbo},
> >                    {in,{{call,{<0.78.0>,#Ref<0.2400559651.2919759873.28633>}},
> >                         a},
> >                        limbo},
> >                    {in,{internal,work},limbo},
> >                    {out,ok,{<0.78.0>,#Ref<0.2400559651.2919759873.28633>}},
> >                    {consume,{{call,{...}},a},limbo,limbo},
> >                    {consume,{internal,work},limbo,limbo}]},
> >                  {"Postponed",[]}]},
> >           {data,[{"State",{limbo,#{}}}]}]]}
> > 4> 
> > 
> > 
> 
> > _______________________________________________
> > erlang-questions mailing list
> > erlang-questions@REDACTED
> > http://erlang.org/mailman/listinfo/erlang-questions
> 
> 
> -- 
> 
> / Raimo Niskanen, Erlang/OTP, Ericsson AB
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions

-- 

/ Raimo Niskanen, Erlang/OTP, Ericsson AB



More information about the erlang-questions mailing list