Search code examples
erlangfsm

erlang gen_statem: error bad_return_from_state_function


I got a strange problem. I got this FSM (The content of the code is not so impotent, so I removed it and now you can look only on the structure):

start_link() ->
  gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).


init([]) ->
  io:format ("init", []),
  Data={1},
  {ok, packetsDeliver, Data}.


callback_mode() ->
  [state_functions, state_enter].


packetsDeliver(enter, _OldState, Data) ->
  io:format ("Key1", []),
  {keep_state, Data};

packetsDeliver(info, _OldState, Data) ->
  io:format ("Key2", []),
  {keep_state, Data};


packetsDeliver(cast, _PacketData, Data) ->
  io:format ("Key3", []),
      {next_state, allPacketsDelivered, Data}.

allPacketsDelivered(enter, _OldState, Data) ->
  io:format ("Key4", []),
  {next_state, packetsDeliver , Data}.

I tried couple of things without any successes, I thought that writing

{next_state, packetsDeliver , Data}.

will deliver me the the state:

packetsDeliver(enter, _OldState, Data)

But instead I got this error:

exception exit: {bad_return_from_state_function,
                       {next_state,packetsDeliver,{1}}}
     in function  gen_statem:loop_event_result/9 (gen_statem.erl, line 1165)
     in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 247)
3> 
=ERROR REPORT==== 27-Jul-2019::23:53:35 ===
** State machine test terminating
** Last event = {cast,1}
** When server state  = {allPacketsDelivered,{1}}
** Reason for termination = error:{bad_return_from_state_function,
                                      {next_state,packetsDeliver,{1}}}
** Callback mode = [state_functions,state_enter]
** Stacktrace =
**  [{gen_statem,loop_event_result,9,[{file,"gen_statem.erl"},{line,1165}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]

Another question/problem is that I don't know why the state

packetsDeliver(enter, _OldState, Data) ->
  io:format ("Key1", []),
  {keep_state, Data};

is delivering me to packetsDeliver(info, _OldState, Data) when I do keep_state?


Solution

  • Look at what your State enter function returns for packetsDeliver:

    packetsDeliver(enter, _OldState, Data) ->
      io:format ("Key1", []),
      {keep_state, Data};   %% <====== HERE
    

    Now look at what your State cast function returns for packetsDeliver:

    packetsDeliver(cast, _PacketData, Data) ->
      io:format ("Key3", []),
      {next_state, allPacketsDelivered, Data}.  %% <===== HERE
    

    Now look at what your State enter function returns for allPacketsDelivered:

    allPacketsDelivered(enter, _OldState, Data) ->
      io:format ("Key4", []),
      {next_state, packetsDeliver , Data}.  %% <==== HERE
    

    The State enter function for allpacketsDelivered should return a tuple that is similar to the State enter function for packetsDelivered, e.g. {keep_state, ...} not {next_state, ...}.

    From the docs, these are the allowed return values for a State enter function:

    state_callback_result(ActionType) = 
        {keep_state, NewData :: data()} |
        {keep_state,
         NewData :: data(),
         Actions :: [ActionType] | ActionType} |
        keep_state_and_data |
        {keep_state_and_data, Actions :: [ActionType] | ActionType} |
        {repeat_state, NewData :: data()} |
        {repeat_state,
         NewData :: data(),
         Actions :: [ActionType] | ActionType} |
        repeat_state_and_data |
        {repeat_state_and_data, Actions :: [ActionType] | ActionType} |
        stop |
        {stop, Reason :: term()} |
        {stop, Reason :: term(), NewData :: data()} |
        {stop_and_reply,
         Reason :: term(),
         Replies :: [reply_action()] | reply_action()} |
        {stop_and_reply,
         Reason :: term(),
         Replies :: [reply_action()] | reply_action(),
         NewData :: data()}
    

    Note that {next_state, ...} is not one of the allowed return values, and that is why you are getting the error bad_return_from_state_function. The docs also say:

    When the gen_statem runs with state enter calls, these functions are also called with arguments (enter, OldState, ...) during every state change. In this case there are some restrictions on the actions that may be returned: postpone() is not allowed since a state enter call is not an event so there is no event to postpone, and {next_event,_,_} is not allowed since using state enter calls should not affect how events are consumed and produced. You may also not change states from this call. Should you return {next_state,NextState, ...} with NextState =/= State the gen_statem crashes. Note that it is actually allowed to use {repeat_state, NewData, ...} although it makes little sense since you immediately will be called again with a new state enter call making this just a weird way of looping, and there are better ways to loop in Erlang. If you do not update NewData and have some loop termination condition, or if you use {repeat_state_and_data, _} or repeat_state_and_data you have an infinite loop! You are advised to use {keep_state,...}, {keep_state_and_data,_} or keep_state_and_data since changing states from a state enter call is not possible anyway.

    ======

    Another question/problem is that I don't know why the state

    packetsDeliver(enter, _OldState, Data) ->
      io:format ("Key1", []),
      {keep_state, Data};
    

    is delivering me to packetsDeliver(info, _OldState, Data) when I do keep_state?

    What does when I do keep_state mean? I can tell you what it means to anyone reading your question: absolutely nothing.

    That info callback doesn't execute when I do keep_state. So that tells you exactly how to fix your code, right? Wrong.

    I'm doing it like this:

    send_event() ->
        gen_statem:cast(?MODULE, hello).
    

    If you do it like this:

    Pid ! {blah, blah}
    

    then the info callback for the current state will execute.

    Full example:

    -module(packets).
    -behavior(gen_statem).
    -compile(export_all).
    
    start_link() ->
        gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).
    
    init([]) ->
        io:format ("init~n"),
        Data=1,
        {ok, packetsDeliver, Data}.  
        %The initial state will be packetsDeliver.
        %And, when you transition to a state, and you 
        %have specified state_enter, then the function
        %State(enter, OldState, Data) will execute, which
        %in this case is packetsDeliver(enter, OldState, Data)
    
    callback_mode() ->
        [state_functions, state_enter].
    
    packetsDeliver(enter, _OldState, Data) ->
        io:format("packetsDeliver enter~n"),
        {keep_state, Data};
    packetsDeliver(cast, _Msg, Data) ->
        io:format ("packetsDeliver cast~n"),
        {next_state, allPacketsDelivered, Data};
    packetsDeliver(info, _Msg, Data) ->
        io:format("packetsDeliver info~n"),
        {keep_state, Data}.
    
    allPacketsDelivered(enter, _OldState, _Data) ->
        io:format("allPacketsDelivered enter~n"),
        %{next_state, packetsDeliver , Data}.
        {keep_state_and_data, []}.
    
    send_event() ->
        gen_statem:cast(?MODULE, hello).
    

    In the shell:

    ~/erlang_programs/gen_statem$ erl
    Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
    Eshell V9.3  (abort with ^G)
    
    1> c(packets).                  
    packets.erl:3: Warning: export_all flag enabled - all functions will be exported
    {ok,packets}
    
    2> packets:start_link().                     
    init
    packetsDeliver enter
    {ok,<0.71.0>}
    
    3> Pid = whereis(packets).
    <0.71.0>
    
    4> Pid ! hello.
    packetsDeliver info
    hello
    
    5> packets:send_event().
    packetsDeliver cast
    ok
    allPacketsDelivered enter
    
    6>