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.
Related
I am trying to perform an action based on changes in the folder which are detected by synrc/fs library. I want to receive this notification every time the changes captured by fs & perform an action e.g. printing the changed filename.
I tried below code but executes only first time!
say_hello() ->
fs:start_link(fs_watcher, "/Users/foldername"),
fs:subscribe(fs_watcher),
receive
{Watcher_process, {Fs, File_event}, {ChangedFile, Type}} ->
io:format("~p was ~p ~n",[ChangedFile,File_event])
end.
Any useful help is appreciated along with link & description if possible! Thanks :)
If you want the function to keep receiving the same kind of messages you could use recursion:
say_hello() ->
fs:start_link(fs_watcher, "/Users/foldername"),
fs:subscribe(fs_watcher),
recur().
recur()->
receive
{Watcher_process, {Fs, File_event}, {ChangedFile, Type}} ->
io:format("~p was ~p ~n",[ChangedFile,File_event]),
recur()
end.
You would have to then think about a way to finalise the function.
You need to recursively call receive:
say_hello() ->
fs:start_link(fs_watcher, "/Users/foldername"),
fs:subscribe(fs_watcher),
loop().
loop() ->
receive
{Watcher_process, {Fs, File_event}, {ChangedFile, Type}} ->
io:format("~p was ~p ~n",[ChangedFile,File_event]),
loop()
end.
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.
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.
Given a function:
%% #doc Retrieves client's state.
-spec(state(pid()) -> atom()).
state(Pid) when is_pid(Pid) ->
case process_info(Pid) of
undefined ->
undefined;
_Else ->
Pid ! {state, self()},
receive
{state, State} ->
State
after
1000 ->
undefined
end
end.
It works as expected for dead pids and for alive clients:
> client:state(A).
undefined
> client:state(Pid).
online
But for some reason returns Pid if process Pid will not reply his status during 1 second:
> client:state(self()).
<0.172.0>
I'm expecting 'undefined' atom there.
How can I fix this code?
This happens because you are receiving the message you sent. Your function is running on the shell process and sends itself the {state, self()} message. Right after sending the message, it receives the message and the function ends with State, which is the self() pid you sent.
I hope I've not been too confusing.
it's continue of previos question
I have gen_server:
start(UserName) ->
case gen_server:start({global, UserName}, player, [], []) of
{ok, _} ->
io:format("Player: " ++ UserName ++ " started");
{error, Error} ->
Error
end
...
How correctly send message to this gen_server. For example: in another file i make:
gen_server:cast(test, message).
In my gen_server file i have:
handle_cast(message, State) ->
io:format("Message receiving \r\n"),
{noreply, State};
I start my gen_server with test name:
server:start(test).
test started
when i call gen_server:cast(test, message). it is nothing output in shell. How can i check handle_cast calling or not?
Thank you.
Instead of
gen_server:cast(test, message).
write
gen_server:cast({global, test}, message).
If you register name as {global, name} you must call it as {global, name}
If your handler is called it will print "Message receiving \r\n" in shell. You made that with io:format call.