Hello i am trying to run a fsm using gen_statem in a separate process from the caller.I am also trying to keep in its state arguments the caller, so it can be updated after each state change.
I keep getting this error :
** State machine <0.123.0> terminating
** Last event = {internal,init_state}
** When server state = {sitting_home,{state,"None",0,0,[<0.116.0>]}}
** Reason for termination = error:{'function not exported',
{fsm,callback_mode,0}}
** Callback mode = undefined
** Stacktrace =
** [{gen_statem,call_callback_mode,1,[{file,"gen_statem.erl"},{line,1610}]},
{gen_statem,enter,7,[{file,"gen_statem.erl"},{line,679}]},
{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,249}]}]
=CRASH REPORT==== 12-Dec-2019::00:14:42.717000 ===
crasher:
initial call: fsm:init/1
pid: <0.123.0>
registered_name: []
exception error: undefined function fsm:callback_mode/0
in function gen_statem:call_callback_mode/1 (gen_statem.erl, line 1610)
in call from gen_statem:enter/7 (gen_statem.erl, line 679)
ancestors: [<0.116.0>]
message_queue_len: 0
messages: []
links: []
dictionary: []
trap_exit: false
status: running
heap_size: 1598
stack_size: 27
reductions: 5805
neighbours:
I have looked in the erlang documentation and i can see no callback mode that needs to be implemented here.
Module
-module(fsm).
-record(state,{
current="None",
intvCount=0,
jobCount=0,
monitor
}).
-export([init/1,start/0]).
-export([hire/2,fire/1,interview/2]).
-behaviour(gen_statem).
start()->
gen_statem:start(?MODULE,[self()],[]).
init(Monitor)->
{ok,sitting_home,#state{current="None",jobCount=0,intvCount=0,monitor=Monitor}}.
sitting_home({intv,Company},State=#state{intvCount=C,monitor=M})->
gen_statem:reply(M,"Got an interview from:"++Company++" going interviewing"),
{next_state,interviewing,State#state{intvCount=C+1}};
sitting_home(Event,State)->
gen_statem:reply(State#state.monitor,{unexpected , Event}),
{next_state,sitting_home,State}.
interviewing({rejected,Company},State)->
gen_statem:reply("Booh got rejected by :"++ Company),
{next_state,sitting_home,State};
interviewing({accepted,Company},State=#state{jobCount=J})->
gen_statem:reply("Hooray got accepted"),
{next_state,working,State#state{jobCount=J+1,current=Company}};
interviewing(_,State)->
gen_statem:reply("Unknown message"),
{next_state,interviewing,State}.
working(fire,State=#state{current=C})->
gen_statem:reply("Unexpected event"),
{next_state,working,State#state{current="None"}};
working(Event,State)->
gen_statem:reply("Unexpected event"),
{next_state,working,State}.
Events
hire(Company,PID)->
gen_statem:sync_send_event(PID,{hire,self(),Company},0).
fire(PID)->
gen_statem:sync_send_event(PID,{fire,self()},0).
interview(Company,PID)->
gen_state:sync_send_event(PID,{intv,Company},0).
P.S I use gen_statem because the shell told me the gen_fsm is deprecated.I do not know the equivalent for sync_send_event.Could this be the problem?
Module:callback_mode is documented at the page you linked. When you have a function for each state (sitting_home, interviewing, etc.) it should be
callback_mode() -> state_functions.
The state functions take 3 arguments, not 2. You are missing the first one, EventType which should be {call, From} in your case.
The equivalent for sync_send_event can just be call, but then your state functions should always reply.
You can combine reply and next_state in the return value of a callback, e.g.
working(Event,State)->
gen_statem:reply("Unexpected event"),
{next_state,working,State}.
can become
working({call, From}, Event, State)->
{next_state, working, State, {reply, From, "Unexpected event"}}.
Even if you don't do this, reply needs a From argument:
working({call, From}, Event, State)->
gen_statem:reply(From, "Unexpected event"),
{next_state,working,State}.
Related
I am trying to update my process's state on a 10 second timer.
-define(INTERVAL, 3000).
start_link() ->
gen_server:start_link(?MODULE, [], []).
action(Pid, Action) ->
gen_server:call(Pid, Action).
init([]) ->
erlang:send_after(?INTERVAL, self(), trigger),
{ok, temple:new()}.
what I want to do is call this
handle_call({fight}, _From, Temple) ->
NewTemple = temple:fight(Temple),
{reply, NewTemple, NewTemple};
So I try
handle_info(trigger, _State) ->
land:action(self(), {fight}),
erlang:send_after(?INTERVAL, self(), trigger);
but I get
=ERROR REPORT==== 4-Dec-2016::19:00:35 ===
** Generic server <0.400.0> terminating
** Last message in was trigger
** When Server state == {{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],
[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],
[]}}},
[]}
** Reason for termination ==
** {function_clause,[{land,terminate,
[{timeout,{gen_server,call,[<0.400.0>,{fight}]}},
{{dict,0,16,16,8,80,48,
{[],[],[],[],[],[],[],[],[],[],[],[],[],[],
[],[]},
{{[],[],[],[],[],[],[],[],[],[],[],[],[],
[],[],[]}}},
[]}],
[{file,"src/land.erl"},{line,47}]}
It appears that with land:action(self(), {fight}), you're attempting to make a call to the same gen_server in which you're currently handling the trigger message. Two important facts will explain why this can't work:
A call always waits for the result to be returned.
A gen_server is a process, and a process can handle only one message at a time.
In handling the trigger message, you're saying to call back to yourself and wait for yourself to process the {fight} message. Since you're in the middle of handling the trigger message, though, you'll never get to the {fight} message. You're effectively in a deadlock with yourself. That's why you're getting a timeout.
P.S. Posting an SSCCE is far more likely to get you good answers.
The error message means that there is no terminate clause in the land server module, you should have a warning when you compile the land server module.
The terminate clause is called because a timeout occurs during the gen_server call with the parameters (Pid = <0.400.0>, Message = {fight}), which was called on line land:action(self(), {fight}),. A call to a gen_server must be completed within a maximum time, by default 5000ms, You have to reduce the time spent in the fight action.
Note that it is not a good idea to increase the server timeout since a gen_server call is blocking: during the execution of a gen_server call no new message can be handled, and in your example , it is also blocking the execution of the handle_info(trigger, _State) code.
Last point, the clause handle_info(trigger, _State) should return a tuple of the form {noreply,NewState} while the last line erlang:send_after(?INTERVAL, self(), trigger); returns a timer reference, you have to modify this line.
I have a function that sets a value to a process Pid and I can have a process depend on another one. So if I set a value to a process then I have to also set the value to the processes that depend on it. However, if there is a circle between the processes
i.e. A depends on B and B depends on A
then I want to return an error message.
I try to do this by passing a list of Pids which have already changed values so that if I come across the same Pid twice (By checking if it is a member of the list of Pids) then the whole function stops. This is my code:
set_values(Pid, Value, PidSet, PidList) ->
case lists:member(Pid, PidList) of
false -> io:format("Setting Value~n"),
lists:map(fun(Pid) ->
Pid ! {self(), set_value, Value, [Pid | PidList]} end, PidSet);
true -> io:format("circle_detected~n"),
Pid ! {circle_detected}
end.
When I run it, I get this error:
=ERROR REPORT==== 2-Nov-2014::17:47:45 ===
Error in process <0.888.0> with exit value: {badarg,[{lists,member,
[<0.888.0>,empty_list],[]},{process,set_viewer_values,4,[{file,"process.erl"},{line,56}]},
{process,looper,2,[{file,"process.erl"},{line,116}]}]}
From what I understand I give bad arguments to lists:member function.
What should I do?
Thanks
If you read your error message, you have {lists,member,
[<0.888.0>,empty_list] ..., where lists is module, member is function name, and [<0.888.0>,empty_list] are aruguments (two) presented as list. So you are making call to lists:nenber/2 with PidList variable being atom empty_list. And this gives you an error.
So you need to look into how you funciton is being called (prefered), or create some pattern match on PidList like
set_values(Pid, Value, PidSet, _PidList = empty_list) ->
...
A basic RabbitMQ install with user guest/guest.
Given the following simple erlang test code for RabbitMQ (erlang client), I am getting the error bellow. The queue TEST_DIRECT_QUEUE exists and has 7 messages in it, and the RabbitMQ server is up and running.
If I try to create a new with a declare API command, I also get a similar error.
Overall the error appears during any the << channel:call >> command
Any thoughts ? Thanks.
=ERROR REPORT==== 16-Feb-2013::10:39:42 ===
Connection (<0.38.0>) closing: internal error in channel (<0.50.0>): shutdown
** exception exit: {shutdown,{gen_server,call,
[<0.50.0>,
{call,{'queue.declare',0,"TEST_DIRECT_QUEUE",false,false,
false,false,false,[]},
none,<0.31.0>},
infinity]}}
in function gen_server:call/3 (gen_server.erl, line 188)
in call from test:test_message/0 (test.erl, line 12)
==============================================
-module(test).
-export([test_message/0]).
-include_lib("amqp_client/include/amqp_client.hrl").
-record(state, {channel}).
test_message() ->
{ok, Connection} = amqp_connection:start(#amqp_params_network{}),
{ok, Channel} = amqp_connection:open_channel(Connection),
Get = #'basic.get'{queue = "TEST_DIRECT_QUEUE"},
{#'basic.get_ok'{}, Content} = amqp_channel:call(Channel, Get), <=== error here
#'basic.get_empty'{} = amqp_channel:call(Channel, Get),
amqp_channel:call(Channel, #'channel.close'{}).
I have identified the issue myself after some frustrating hours. Overall, let me confess to be upset with the vague tutorias and documentation about RabbitMQ.... Anyways, here is what the problem was:
1) Queue Names are supposed to be in binary form, therefore preceded by "<<" and superceded by ">>". For example : <<"my queue name">> ( quotes included as well )
2) In a different scenario where I was trying to create the queue with queue.declare, the fact that the queue already existed was not a problem, but the fact that the queue was durable and the queue.declare did not specify that set of parameters caused the program to throw an error and interrupt execution. This is an unfortunate behavior where normally, developers would expect the queue matching to be done simply by name and then proceed. So to fix that I had to specify the durable value.
Here is a simple working code:
-module(test).
-export([test/0]).
-include_lib("amqp_client/include/amqp_client.hrl").
test() ->
{ok, Connection} = amqp_connection:start(#amqp_params_network{}),
{ok, Channel} = amqp_connection:open_channel(Connection),
Declare = #'queue.declare'{queue = <<"TEST_DIRECT_QUEUE">>, durable = true},
#'queue.declare_ok'{} = amqp_channel:call(Channel, Declare),
Get = #'basic.get'{queue = <<"TEST_DIRECT_QUEUE">>, no_ack = true},
{#'basic.get_ok'{}, Content} = amqp_channel:call(Channel, Get),
#amqp_msg{payload = Payload} = Content.
I try to cast message to a gen_server:
gen_server:cast({global, ID}, {watchers}).
The handler is:
handle_cast({watchers}, State) ->
case State#table_state.watchers of
[] ->
{reply, no_watchers, State};
_ ->
{reply, State#table_state.watchers, State}
end;
But when I execute gen_server:cast the gen_server terminates with error:
=ERROR REPORT==== 29-Apr-2011::18:26:07 ===
** Generic server 1 terminating
** Last message in was {'$gen_cast',{watchers}}
** When Server state == {table_state,1,"1",11,[]}
** Reason for termination ==
** {bad_return_value,{reply, no_watchers, {table_state,3,"3",11,[]}}}
Why do I get bad_return_value?
You cannot reply using cast (see gen_server documentation). That is the whole point of casting an asynchronous message instead of using call.
In your case you want to return a reply, so use gen_server:call/2 instead.
I need to send a message to a globally registered process, that might be unavailable for a short-time, when it is replaced with a backup process (i.e. failover).
Is the following snippet good Erlang code:
% send message to globally registered process, with possibility to retry once
send_message(To, Message, Retry) ->
try global:send(To, Message)
catch
% registered process To is unavailable
exit: {badarg, {To, Message}} ->
io:format("catch: exit: {badarg,{~w, ~w}}~n", [To, Message]), % dbg only
case Retry of
true ->
% retry sending message, after 1 second
sleep(1000),
send_message(To, Message, false);
false ->
% re-throw caught exit, including stack trace
erlang:raise(exit, {badarg, {To, Message}},
erlang:get_stacktrace())
end
end.
The Retry parameter is either true or false, indicating the message should be retried once if there was a problem. If the message can still not be sent, I want the same exception to be raised as would have been the case by calling global:send(To, Message) outside a try-catch block.
I know the above works, but I am concerned whether the false section of my case block is good erlang (e.g. using erlang:raise() and rlang:get_stacktrace()).
Any thoughts or suggestions to make the code "better"?
Make two different calls to global:send, one inside a try ... catch, the other not:
send_message(To, Message, 0, _, _) ->
global:send(To, Message);
send_message(To, Message, RetriesLeft, RetryDelayMs, MaxRetryDelayMs) ->
try
global:send(To, Message)
catch
% registered process To is unavailable
exit: {badarg, {To, Message}} ->
io:format("catch: exit: {badarg,{~w, ~w}}~n", [To, Message]), % dbg only
% retry after RetryDelayMs milliseconds
sleep(min(RetryDelayMs, MaxRetryDelayMs)),
send_message(To, Message, RetriesLeft - 1, 2 * RetryDelayMs, MaxRetryDelayMs)
end.
EDIT: Added exponential back-off. Be like … nope, just can't do it.
I would do
erlang:error({badarg,{To,Message}})
instead. No real difference as this also generates a stack trace but I think it is clearer. erlang:raise/3 is better for a more generic usage and if you want do things with the stacktrace.