I got a set of tests that the program should pass and all the local tests works just fine with my server it's when I try to run the remote tests that the server crashes.
The crash message is the following:
=ERROR REPORT==== 23-Jul-2015::23:59:17 === Error in process <0.39.0> on
node 'nodeS#127.0.0.1' with exit value:
{undef,[{genserver,start,[server, {server_st,[],[]},#Fun<server.loop.2>],[]}]}
My start-up function looks as following:
loop(St, {From, Nick, connection_wanted}) ->
case lists:keymember(Nick, 2, St#server_st.users) of
false -> {ok, St#server_st{users = St#server_st.users ++ [{From, Nick}]}};
true -> {{user_already_connected, St}, St}
end;
With the record "server_st" is defined as:
-record(server_st, {users = [], channels = []}).
Finally the genserver start&loop function is:
start(Name, State, F) ->
Pid = spawn(fun() -> loop(State, F) end),
register(Name, Pid),
Pid.
loop(State, F) ->
receive
{request, From, Ref, Data} ->
case catch(F(State, Data)) of
{'EXIT', Reason} ->
From!{exit, Ref, Reason},
loop(State, F);
{R, NewState} ->
From!{result, Ref, R},
loop(NewState, F)
end;
{update, From, Ref, NewF} ->
From ! {ok, Ref},
loop(State, NewF);
stop ->
true
end.
Then genserver functions I'm not allowed to change. If needed I can post the whole testsuite too.
Edit
Digging a bit further into the test cases and I'm unsure if it really is the server that's causing the issue, my remote connect function looks as following:
loop(St, {connect, {_Server, _Machine}}) ->
ServerPID = {list_to_atom(_Server), list_to_atom(_Machine)},
case genserver:request(ServerPID, {self(), St#cl_st.nick, connection_wanted}) of
ok -> {ok, St#cl_st{connected_to = ServerPID}};
_ -> {{error, user_already_connected, "A user with the nick " ++ St#cl_st.nick ++ "is already connected to" ++ _Server}, St}
end;
Edit 2
Found the specific row inside the testsuite that's causing the error:
-define(HOST, '127.0.0.1').
new_client(Nick, GUIName) ->
ClientName = test_client:find_unique_name("client_"),
ClientAtom = list_to_atom(ClientName),
% Row below is causing the error
Result = slave:start(?HOST, ClientAtom),
assert_ok("start client node "++ClientName, element(1,Result)),
ClientNode = element(2,Result),
InitState = client:initial_state(Nick, GUIName),
Result2 = spawn(ClientNode, genserver, start, [ClientAtom, InitState, fun client:loop/2]),
assert("client startup "++ClientName, is_pid(Result2)),
{Nick, ClientAtom, ClientNode}.
Your function genserver:start/3 is most probably not exported or module genserver is not available at the node where you run code which calls it.
Solved it, was in a completely unrelated part where the client is communicating with other users. Still used the whereis command to locate other users from an older version of the program.
Related
I need to maintain a shared queue where I can push data and a separate thread will periodically check and pull data from the queue if its not empty. I came up with the below solution where I can send data to the process and it adds up to a list. However, is there a cleaner / easier solution to do this ?
And im not sure how I may pull data from the below code.
-module(abc).
-export(queue/0).
queue() ->
receive
{push, Xmpp} ->
io:format("Push"),
queue(Xmpp);
{pull} ->
io:format("pull"),
queue()
end.
queue(E) ->
receive
{push, Xmpp} ->
io:format("Push ~w",[E]),
E1 = lists:append([E],[Xmpp]),
queue(E1);
{reset} ->
queue([])
end.
The code probably won't do exactly what you want as written. When you call queue/1 from the receive block ( queue(Xmpp); in line 7), queue/1 will fire and then wait for a message. Because this is not spawned in a separate process, queue/0 will block (since queue/1 is now waiting for a message that is never sent).
Also, queue/0 won't ever send anything back to the process sending it messages. It has no way of returning data to the sender.
The following will work (you will need to send messages to the pid returned by queue/0).
-module(abc).
-export([queue/0,queue/1]).
queue() ->
%% initialize an empty queue,
%% return the Pid to the caller
spawn(abc,queue,[[]]).
queue(E) when is_list(E) ->
receive
%% append the message value to the existing list
{push, Xmpp} ->
io:format("Pushing ~w to ~w~n",[Xmpp,E]),
E1 = lists:append(E,[Xmpp]),
queue(E1);
%% reset the queue
{reset} ->
queue([]);
%% return the value to the caller
%% "Pid" must be a Pid
{pull, Pid} when is_pid(Pid) ->
io:format("pull~n"),
Pid ! E,
queue(E)
end.
That's a problem with a straightforward solution in Erlang. Most of the time every erlang module you will be programming will be like a server, which will expect messages and will answer, you can have 0, 1 or mulitple servers running the same erlang module code. At the same time, you will be programming in the same module a client, which is an easy way to send messages to the server without having to know all the message format the server expect, instead you use functions, so instead of doing stuff like
Server ! {put, Value},
receive
{Server, {ok, Value}} ->
everything_ok;
{Server, {error, Reason}} ->
handle_error
end,
you end doing something like
my_module:put(Server, Value).
So you can create a server process in erlang with the code:
-module(abc).
-export([start/0, put/2, pop/1, reset/1, is_empty/1, loop/1]).
%% Client
start() ->
spawn(?MODULE, loop, [[]]).
put(Pid, Item) ->
Pid ! {self(), {put, Item}},
receive
{Pid, {ok, Item}} ->
Item;
{Pid, {error, Reason}} ->
{error, Reason}
after 500 ->
timeout
end.
pop(Pid) ->
Pid ! {self(), {pop}},
receive
{Pid, {ok, Item}} ->
Item;
{Pid, {error, Reason}} ->
{error, Reason}
after 500 ->
timeout
end.
reset(Pid) ->
Pid ! {self(), {reset}},
receive
{Pid, {ok}} ->
ok;
_ ->
error
after 500 ->
timeout
end.
is_empty(Pid) ->
Pid ! {self(), {is_empty}},
receive
{Pid, {true}} ->
true;
{Pid, {false}} ->
false;
_ ->
error
after 500 ->
timeout
end.
%% Server
loop(Queue) ->
receive
{From, {put, Item}} when is_pid(From) ->
From ! {self(), {ok, Item}},
loop(Queue ++ [Item]);
{From, {pop}} when is_pid(From) ->
case Queue of
[] ->
From ! {self(), {error, empty}},
loop(Queue);
[H|T] ->
From ! {self(), {ok, H}},
loop(T)
end;
{From, {reset}} when is_pid(From) ->
From ! {self(), {ok}},
loop([]);
{From, {is_empty}} when is_pid(From) ->
case Queue of
[] ->
From ! {self(), {true}},
loop(Queue);
_ ->
From ! {self(), {false}},
loop(Queue)
end;
_ ->
loop(Queue)
end.
and the code you end writting to use is simple as well:
(emacs#rorra)1> c("/Users/rorra/abc", [{outdir, "/Users/rorra/"}]).
{ok,abc}
(emacs#rorra)2> Q = abc:start().
<0.44.0>
(emacs#rorra)3> abc:is_empty(Q).
true
(emacs#rorra)4> abc:pop(Q).
{error,empty}
(emacs#rorra)5> abc:put(Q, 23).
23
(emacs#rorra)6> abc:is_empty(Q).
false
(emacs#rorra)7> abc:pop(Q).
23
(emacs#rorra)8> abc:pop(Q).
{error,empty}
(emacs#rorra)9> abc:put(Q, 23).
23
(emacs#rorra)10> abc:put(Q, 50).
50
(emacs#rorra)11> abc:reset(Q).
ok
(emacs#rorra)12> abc:is_empty(Q).
true
At the end to avoid all that repeated code you end using OTP and writting a gen_server for it.
I'm assuming you are building a queue on your own for learning, otherwise Erlang already has a good implementation for a Queue:
http://www.erlang.org/doc/man/queue.html
And the source code:
https://github.com/erlang/otp/blob/master/lib/stdlib/src/queue.erl
I'm having some trouble with an Erlang module. Here is the one that I wrote:
-module(basic_gen_server).
-export([start/1, call/2, cast/2]).
start(Module) ->
register(server, spawn(basic_gen_server,gen_server_loop,[Module, Module:init()])), server.
call(Pid,Request) ->
Pid ! {call, self(), Request},
receive
Reply -> Reply
end.
cast(Pid,Request) ->
Pid ! {cast, self(), Request},
receive
_ -> ok
end.
gen_server_loop(Module, CurrentState) ->
io:fwrite("gen_server_loop~n", []),
receive
{call, CallPid, Request} ->
{reply, Reply, NewState} = Module:handle_call(Request,self(),CurrentState),
CallPid ! Reply,
gen_server_loop(Module, NewState);
{cast, CastPid, Request} ->
{noReply, NewState} = Module:handle_cast(Request, CurrentState),
CastPid ! noReply,
gen_server_loop(Module, NewState)
end.
And here is the callback module that was defined:
% Written by Caleb Helbling
% Last updated Oct 10, 2014
-module(name_server).
-export([init/0, add/3, whereis/2, handle_cast/2,
handle_call/3, handle_swap_code/1]).
%% client routines
add(ServerPid, Person, Place) ->
basic_gen_server:cast(ServerPid, {add, Person, Place}).
whereis(ServerPid, Person) ->
basic_gen_server:call(ServerPid, {whereis, Person}).
%% callback routines
init() ->
maps:new().
handle_cast({add, Person, Place}, State) ->
NewState = maps:put(Person, Place, State),
{noreply, NewState}.
handle_call({whereis, Person}, _From, State) ->
Reply = case maps:find(Person, State) of
{ok, Place} -> Place;
error -> error
end,
NewState = State,
{reply, Reply, NewState}.
handle_swap_code(State) ->
{ok, State}.
Upon trying to initialize the server with the following command:
MyServer = basic_gen_server:start(name_server).
I get the following debug output:
=ERROR REPORT==== 29-Oct-2014::12:41:42 ===
Error in process <0.70.0> with exit value: {undef,[{basic_gen_server,gen_server_loop,[name_server,#{}],[]}]}
Conceptually, I understand the notion of making serial code into a basic server system, but I believe that I have a syntax error that I haven't been able to find using either syntax highlighting or Google. Thanks in advance for the help!
Function gen_server_loop is not exported. So you can not call basic_gen_server:gen_server_loop(Module, Module:init()), which is what is happening inside spawn(basic_gen_server,gen_server_loop,[Module, Module:init()]).
If you read your error message it tells you that function you are trying to call in undefined (trougn undef atom). Function being {basic_gen_server,gen_server_loop,[name_server,#{}],[]}, or where you have {Module, Function, ListOfArgs, ...}. You always should check that
there are no types module or function name
function arity match number of arguments in call (List in error message)
function is exported
All local calls (like loop(SomeArgs), without module specified) will not compile if function is not defined. And you can do local call dynamically (FuntionName(SomeArgs) again without module name).
EDIT after comment about need of local calls.
You actually could use lambda for this. There is spawn/1 funciton, which takes lambda (or fun if you like), so you can call spawn( fun local_functino/0).. Only issue with that is fact that your fun can not take any arguments, but there is a way around it, with use of closures.
spawn(fun () ->
gen_server_loop(Module, Module:init())
end).
And gen_serve_loop stays local call.
I have some issue with distributed tests under rebar.
Rebar starts node with name nonode#nohost. After it I call help function make_distrib which provide normal node name and starts distribution work.
After starting the slave node I couldn't sent to it any lambda. I had error:
=ERROR REPORT==== 27-Jul-2013::22:48:02 ===
Error in process on node 'test1#just' with exit value: {{badfun,#Fun<msg_proxy_tests.2.117197241>},[{error_handler,undefined_lambda,3,[{file,"error_handler.erl"},{line,64}]}]}
But! If I run it test with simple way - all works good:
$erl
1> c(msg_proxy_tests).
{ok,msg_proxy_tests}
2> eunit:test({module, msg_proxy_tests},[verbose]).
======================== EUnit ========================
module 'msg_proxy_tests'msg_proxy_tests: distrib_mode_test_ (distribute mode test for nodes)...
msg_proxy_tests.erl:14:<0.48.0>: nodes ( ) = [test1#just]
msg_proxy_tests.erl:15:<9999.39.0>: node ( ) = test1#just
msg_proxy_tests.erl:17:<0.48.0>: nodes ( ) = [test1#just]
[0.238 s] ok
How can I fix this?
Module source:
-module(msg_proxy_tests).
-include_lib("eunit/include/eunit.hrl").
distrib_mode_test_()->
{"distribute mode test for nodes", timeout, 60,
fun() ->
{ok, Host} = inet:gethostname(),
make_distrib("tests#"++Host, shortnames),
slave:start(Host, test1),
?debugVal(nodes()),
spawn(list_to_atom("test1#"++Host), fun()-> ?debugVal(node()) end),
timer:sleep(100),
?debugVal(nodes()),
stop_distrib(),
ok
end}.
-spec make_distrib( NodeName::string()|atom(), NodeType::shortnames | longnames) ->
{ok, ActualNodeName::atom} | {error, Reason::term()}.
make_distrib(NodeName, NodeType) when is_list(NodeName) ->
make_distrib(erlang:list_to_atom(NodeName), NodeType);
make_distrib(NodeName, NodeType) ->
case node() of
'nonode#nohost' ->
[] = os:cmd("epmd -daemon"),
case net_kernel:start([NodeName, NodeType]) of
{ok, _Pid} -> node()
end;
CurrNode -> CurrNode
end.
stop_distrib()->
net_kernel:stop().
That is because of module hash on both nodes is different. That happens because eunit compiles code before it run and module contains -ifdef(TEST) macroses, which will definitely change it's hash.
Thats why anonymous function #Fun<msg_proxy_tests.2.117197241 could not be called and error occurred. (look at function name and you will notice funny numbers, this is module hash, it will be different on both nodes).
If you want to avoid this, you should call funs by it fully qualified name: module:fun_name/arity.
I'm running the following code with dbg:p(client, r):
-module(client).
-export([start/0, start/2, send/1, net_client/1]).
start() ->
start("localhost", 7000).
start(Host, Port) ->
io:format("Client connecting to ~p:~p.~n", [Host, Port]),
register(ui, spawn(fun() -> gui_control([]) end)),
case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of
{ok, Socket} ->
Pid = spawn(client, net_client, [Socket]),
register(client, Pid),
gen_tcp:controlling_process(Socket, Pid);
Error ->
io:format("Error connecting to server: ~p~n", [Error]),
erlang:error("Could not connect to server.")
end,
ok.
send(Msg) ->
client!{send, Msg}.
%% Forwards messages to either the GUI controller or the server.
net_client(Socket) ->
receive
{tcp, Socket, Message} ->
Msg = binary_to_term(Message),
io:format("Received TCP message on ~p: ~p~n", [Socket, Msg]),
ui!{server, Msg};
{send, Message} ->
io:format("Sending ~p.~n", [Message]),
gen_tcp:send(Socket, term_to_binary(Message));
close ->
gen_tcp:close(Socket),
exit(normal);
{tcp_closed, Socket} ->
io:format("Server terminated connection.~n"),
exit(normal); %% Reconnect?
timeout -> %% This
io:format("Timed out?~n");
{inet_reply, Socket, Message} -> %% and this never happen.
io:format("inet_reply: ~p~n", Message);
Error ->
io:format("Net client got bad message: ~p.~n", [Error])
after 10000 ->
refresh %% gen_tcp:send(Socket, term_to_binary(keepalive))
end,
?MODULE:net_client(Socket).
gui_control(Data) ->
receive
%% This will hang the gui until the sync is done. Not sure if
%% that's okay.
{server, {sync, Datum}} -> % Resync command from server.
gui_control(resync([Datum]));
{client, refresh} -> % Refresh request from display.
display:update(Data);
{server, Msg} ->
io:format("UI Rx: ~p~n", [Msg])
end,
gui_control(Data).
resync(Data) ->
receive
{server, {sync, Datum}} ->
resync([Datum|Data]);
{server, {done, Num}} ->
case length(Data) of
Num ->
Data;
_ ->
io:format("Got done before all the data were received.~n"),
send({sync})
end
after 5000 ->
io:format("Timed out waiting for data.~n"),
send({sync})
end.
It communicates with a server I wrote with gen_tcp and gen_server, following this. My main problem is that I don't reliably receive all my messages. Sometimes I'll get
(<0.2.0>) << {tcp,#Port<0.517>,
<<131,104,6,100,0,4,99,97,114,100,100,0,7,117,110,107,110,
111,119,110,100,0,7,117,110,107,110,111,119,110,106,106,
104,3,107,0,6,83,101,99,111,110,100,100,0,4,100,114,97,
119,97,2,131,104,6,100,0,4,99,97,114,100,100,0,7,117,110,
107,110,111,119,110,100,0,7,117,110,107,110,111,119,110,
106,106,104,3,107,0,6,83,101,99,111,110,100,100,0,4,100,
114,97,119,97,3,131,104,6,100,0,4,99,97,114,100,100,0,7,
117,110,107,110,111,119,110,100,0,7,117,110,107,110,111,
119,110,106,106,104,3,107,0,5,70,105,114,115,116,100,0,4,
100,114,97,119,97,0>>}
from the debugging output, but no corresponding Received TCP message on #Port<0.517>:... message. I'll also see things like this:
(<0.2.0>) << {io_reply,<0.24.0>,ok}
(<0.2.0>) << timeout
(<0.2.0>) << {io_reply,<0.24.0>,ok}
(<0.2.0>) << timeout
(<0.2.0>) << {io_reply,<0.24.0>,ok}
but nothing from net_client's receive. I've watched the network traffic with wireshark and I know the packets are getting where they're supposed to go and being ACKed. What am I doing wrong?
Edit: I'm invoking this with erl -smp enable -eval "client:start()." in case it matters.
I guess the basic problem is that 'net_client' should be spawned off as a separate process..
In the start method, change
register(client, self()),
net_client(Socket);
to
register(client, fun() -> net_client(Socket) end);
that should solve it..
Also, I recommend using redbug (part of eper) https://github.com/massemanet/eper when tracing. It protects you from drowning your system in trace output and provides a dead simple syntax, eg: redbug:start("mymod:foo -> return", [{msgs,10}]). trace all calls to mymod:foo and what those calls return but give me no more than 10 trace messages.
Turns out {packet, 0} was my problem. Replace that with {packet, 2} and all is well.
As I learn Erlang, I'm trying to solve ex. 4.1 ("An Echo server") from "Erlang Programming" book (by O'Reilly) and I have a problem.
My code looks like that:
-module(echo).
-export([start/0, print/1, stop/0, loop/0]).
start() ->
register(echo, spawn(?MODULE, loop, [])),
io:format("Server is ready.~n").
loop() ->
receive
{print, Msg} ->
io:format("You sent a message: ~w.~n", [Msg]),
start();
stop ->
io:format("Server is off.~n");
_ ->
io:format("Unidentified command.~n"),
loop()
end.
print(Msg) -> ?MODULE ! {print, Msg}.
stop() -> ?MODULE ! stop.
Unfortunatelly, I have some problems. Turning on works as expected, it spawns a new process and display "Server is ready" message. But when I try to use print function (like echo:print("Some message."). that, for example) I got result, but it doesn't work like I'd like to. It prints my message as a list (not as a string) and it generates
=ERROR REPORT==== 18-Jul-2010::01:06:27 ===
Error in process <0.89.0> with exit value: {badarg,[{erlang,register,[echo,<0.93.0>]},{echo,start,0}]}
error message.
Moreover, when I try to stop server by echo:stop() I got another error
** exception error: bad argument
in function echo:stop/0
Could anybody explain me, what's going on here ? I am new to Erlang and it seems to be quite difficult to grasp for me at this time.
When your loop/0 function receive print message you call start/0 again which spawns new process and trying to register it as echo again. It causes your server dies and new one is not registered as echo, so you can't send message to it by print/1 function any more.
loop() ->
receive
{print, Msg} ->
io:format("You sent a message: ~w.~n", [Msg]),
loop(); % <-- just here!
stop ->
io:format("Server is off.~n");
_ ->
io:format("Unidentified command.~n"),
loop()
end.