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?
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, ...}
withNextState =/= 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>