I'm slowly learning erlang language using learnyousomeerlang site and I'm currently at "Rage Against The Finite-State Machines" chapter, which builds and describes how trade_fsm.erl works. As a part of my learning process I've decided to write an interface for this system, where you can control both trading sides by typing console commands. I think I've done a decent job at writing that, however for some reason I cannot understand, whenever I try to start trading, the clients crash. Here's how it goes:
5> z3:init("a", "b").
true
6> z3:display_pids().
First player pid: {<0.64.0>}
Second player pid: {<0.65.0>}.
done
7> z3:p1_propose_trade().
{a}: asking user <0.65.0> for a trade
{b}: <0.64.0> asked for a trade negotiation
done
8> z3:display_pids().
done
9>
And here's my code:
-module(z3).
-compile(export_all).
-record(state, {player1,
player2,
p1items=[],
p2items=[],
p1state,
p2state,
p1name="Carl",
p2name="FutureJim"}).
init(FirstName, SecondName) ->
{ok, Pid1} = trade_fsm:start_link(FirstName),
{ok, Pid2} = trade_fsm:start_link(SecondName),
S = #state{p1name=FirstName, p2name=SecondName,
player1=Pid1, player2=Pid2,
p1state=idle, p2state=idle},
register(?MODULE, spawn(?MODULE, loop, [S])).
display_pids() ->
?MODULE ! display_pids,
done.
p1_propose_trade() ->
?MODULE ! {wanna_trade, p1},
done.
p2_accept_trade() ->
?MODULE ! {accept_trade, p2},
done.
loop(S=#state{}) ->
receive
display_pids ->
io:format("First player pid: {~p}~nSecond player pid: {~p}.~n", [S#state.player1, S#state.player2]),
loop(S);
{wanna_trade, Player} ->
case Player of
p1 ->
trade_fsm:trade(S#state.player1, S#state.player2);
p2 ->
trade_fsm:trade(S#state.player2, S#state.player1);
_ ->
io:format("[Debug:] Invalid player.~n")
end,
loop(S);
{accept_trade, Player} ->
case Player of
p1 ->
trade_fsm:accept_trade(S#state.player1);
p2 ->
trade_fsm:accept_trade(S#state.player2);
_ ->
io:format("[Debug:] Invalid player.~n")
end,
loop(S);
_ ->
io:format("[Debug:] Received invalid command.~n"),
loop(S)
end.
Can anyone tell me why this code fails and how it should be implemented?
when you call z3:p1_propose_trade(). it sends the message {wanna_trade, p1} to registered process z3.
The message is interpreted in the loop function which calls trade_fsm:trade(S#state.player1, S#state.player2); converted into gen_fsm:sync_send_event(S#state.player1, {negotiate, S#state.player2}, 30000).. This call is a synchronous call which is waiting for a reply from the fsm, and which timeout after 30 seconds if it did not receive any answer.
In the state wait, you have caught the message in the statement:
idle({negotiate, OtherPid}, From, S=#state{}) ->
ask_negotiate(OtherPid, self()),
notice(S, "asking user ~p for a trade", [OtherPid]),
Ref = monitor(process, OtherPid),
{next_state, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
No reply value is returned to the caller. You should have used in the last line something like
{reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
or an explicit call to gen_fsm:reply/2.
I didn't dig too much in the code, but if you change it to:
idle({negotiate, OtherPid}, From, S=#state{}) ->
Reply = ask_negotiate(OtherPid, self()),
notice(S, "asking user ~p for a trade", [OtherPid]),
Ref = monitor(process, OtherPid),
{reply, Reply, idle_wait, S#state{other=OtherPid, monitor=Ref, from=From}};
it doesn't stop and seems to work properly.
Maybe some one knowing perfectly the behavior of the gen_fsm can give an explanation of what is going behind the scene (why is there nothing printout when the timeout ends, why the shell is ready for a new command while it should be waiting for an answer?):
If you call manually the function trade(OwnPid, OtherPid) you will see that it doesn't return until the 30 second timeout is reached, and then you get an error message.
when it is called by z3:p1_propose_trade()., after 30 seconds the error message is not shown but the registered process z3 dies.
[EDIT]
I have checked how the code should work, and, in fact, it doesn't seem necessary to modify the fsm code. The reply should come from the process 2, when the second user accept to negotiate. So you can't do the test this way (loop is waiting for an answer, and it cannot send the accept_trade). here is a session that works:
{ok,P1} = trade_fsm:start("a1").
{ok,P2} = trade_fsm:start("a2").
T = fun() -> io:format("~p~n",[trade_fsm:trade(P1,P2)]) end.
A = fun() -> io:format("~p~n",[trade_fsm:accept_trade(P2)]) end.
spawn(T). % use another process to avoid the shell to be locked
A().
You can change the "wanna_trade" interface to avoid the blocking issue
{wanna_trade, Player} ->
case Player of
p1 ->
spawn(fun() -> trade_fsm:trade(S#state.player1, S#state.player2) end);
p2 ->
spawn(fun() -> trade_fsm:trade(S#state.player2, S#state.player1) end);
_ ->
io:format("[Debug:] Invalid player.~n")
end,
loop(S);
Related
I am working with concurrent programming in Erlang where i am trying to exchange messages between two users. So i have hardcoded one set of messages which sent as message one by one from user1 to another user2, i am reading the input from keyboard and pass that input as reply to the user1. But while executing it the input is not taken and the message passing continues without input and asks for the input at last after completion of all processes.
-module(third).
-import(lists,[nth/2]).
-export([start/0,user2/5,user1/3,inp/1]).
inp(P) ->
{ok, [S]} = io:fread("entry: \n", "~s"),
P ! S.
user2(0, USERID1,_,_,_) ->
USERID1 ! bye,
io:format("finish");
user2(N, USERID1,Mess,Rep,K) ->
USERID1 ! {message, self(),Mess,K},
receive
reply ->
S=self(),
spawn(third, inp, [S])
end,
user2(N-1, USERID1,Mess,Rep,K+1).
user1(Mess,Rep,K) ->
receive
bye ->
io:format("conversation over");
{message, USERID2,Mess,K} ->
io:format("~p~n",[nth(K,Mess)]),
USERID2 ! reply,
user1(Mess,Rep,K+1)
end.
start() ->
Mess=["HEY","sup","how are you","yhank you","bye"],
USERID1=spawn(third, user1, [Mess,Rep,1]),
spawn(third, user2, [5,USERID1,Mess,Rep,1]).
How do i wait for the input and then pass on that message.
When executed the start module the output is as follows -
1> c(third).
{ok,third}
2> third:start().
"HEY"
<0.77.0>
entry:
"sup"
entry:
entry:
"how are you"
entry:
entry:
"yhank you"
entry:
entry:
"bye"
entry:
finishentry:
entry:
conversation overentry:
When it is prompting for entry , it should wait for the entry and then proceed.
The version given later works as you expect - at least I think so, because there are not a lot of comment neither in your question, nor in your code.
it contains 2 modifications:
As said Odobenus, if you spawn the function inp, the function user2
gets imediately the return value of spawn which is the pid of the
spawned process, not the string entered by the user. Thus user2
doesn't wait for anything else. The process still exists, so it will
prompt "entry:", wait for an input, but it won't use it, and the
string is lost as soon as entered.
There is another issue at the last line, when you spawn the function user2. Again, the start
function stops immediately when it has got the pid of the new
process; as it was started by a shell command, the shell takes the
control of the io immediately, and the user2 process will never get
the string entered by the user since it will be interpreted as a command by the shell.
'''
-module(third).
-export([start/0,user2/4,user1/2,inp/1]).
inp(P) ->
S = io:get_line("entry: \n"),
P ! S.
user2(0, USERID1,_,_) ->
USERID1 ! bye,
io:format("finish");
user2(N, USERID1,Mess,K) ->
USERID1 ! {message, self(),Mess,K},
io:format("user2 wait for reply~n"),
receive
reply ->
S=self(),
inp(S)
end,
user2(N-1, USERID1,Mess,K+1).
user1(Mess,K) ->
receive
bye ->
io:format("conversation over");
{message, USERID2,Mess,K} ->
io:format("~p~n",[lists:nth(K,Mess)]),
USERID2 ! reply,
user1(Mess,K+1)
end.
start() ->
Mess=["HEY","sup","how are you","yhank you","bye"],
USERID1=spawn(third, user1, [Mess,1]),
user2(5,USERID1,Mess,1).
'''
However, in your example the two user processes do not really communicate: they do not examine the messages, and they exchange each time the complete collection of messages. I propose you to examine this other version, bye default, after 5 exchanges, user 1 close the chat, but user 2 can stop it before by entering the message "bye".
'''
-module(third).
-export([start/0,user2/1,user1/1]).
user2(USERID1) ->
S = io:get_line("entry: \n"),
USERID1 ! {message, self(),S},
io:format("user2 wait for reply~n"),
maybe_continue_user2(USERID1,S).
maybe_continue_user2(_,"bye\n") ->
io:format("User2 ends the chat~n");
maybe_continue_user2(USERID1,_) ->
receive
{reply,"bye"} ->
io:format("bye, close chat~n");
{reply,Mess} ->
io:format("user2 got ~p~n",[Mess]),
user2(USERID1)
end.
user1([]) ->
io:format("User1 ends the chat~n");
user1([H|T]) ->
receive
{message,_,"bye\n"} ->
io:format("user 1: conversation over~n");
{message, USERID2,Mess} ->
io:format("~p~n",[Mess]),
USERID2 ! {reply,H},
user1(T)
end.
start() ->
Mess=["HEY","sup","how are you","thank you","bye"],
USERID1=spawn(third, user1, [Mess]),
user2(USERID1).
'''
an example where user 2 stops the chat:
9> third:start().
entry:
hi
user2 wait for reply
"hi\n"
user2 got "HEY"
entry:
how are you?
user2 wait for reply
"how are you?\n"
user2 got "sup"
entry:
bye
user2 wait for reply
user 1: conversation over
User2 ends the chat
ok
10>
See if this helps:
-module(my).
-compile(export_all).
start() ->
register(user1, spawn(my, user1, [["Hello!", "How are you?", "Bye"]]) ),
register(user2, spawn(my, user2, []) ),
start_finished.
user1([]) ->
{user1, terminated};
user1([String|Strings])->
timer:sleep(500), %% See discussion below.
user2 ! String,
receive
Reply -> io:format("user1 received: ~s~n", [Reply])
end,
user1(Strings).
user2() ->
receive
"Bye" -> io:format("Bye~n");
Msg ->
Prompt = io_lib:format("~s~n>>> ", [Msg]),
Reply = io:get_line(Prompt),
user1 ! Reply,
user2()
end.
In the shell:
~/erlang_programs$ 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(my).
my.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,my}
2> my:start().
start_finished
Hello!
>>> Hi there!
user1 received: Hi there!
How are you?
>>> Good, and you?
user1 received: Good, and you?
Bye
3>
I'm not sure why I need the timer:sleep() call, but it prevents some type of race condition that causes io:get_line() to malfunction.
the code that reads from stdin. You launch it in the separated process
spawn(third, inp, [S])
every time when user2 process receives 'reply' atom
And btw result of user input will be send to user2 process and will never used.
I understand that Erlang is all about concurrency and we use spawn/spawn_link to create a process what I don't understand is how can all processes use one common list of users concurrently? say an ordict/dict storage.
What I am trying to do is;
1. A Spawned User process Subscribes/Listens to registered process A
2. Registered process A stores {Pid, Userid} of all online users
3. When some user sends a message user's process asks process A wether recipient is online or not.
sending a message in erlang is asynchronous but is it also asynchronous when a user is being sent messages by multiple users?
You can make process A a gen_server process and keep any data structure storing online users as the process state. Storing a new user or deleting one could be done with gen_server:cast/2, and checking to see if a user is online could be done with gen_server:call/2. Alternatively, you could have a gen_server create a publicly-readable ets table to allow any process to read it to check for online users, but storing and deleting would still require casts to the gen_server. You could even make the table publicly readable and writable so that any process could store, delete, or check users. But keep in mind that an ets table is by default destroyed when the process that creates it dies, so if you need it to stay around even if the gen_server that created it dies, you must arrange for it to be inherited by some other process, or give it to a supervisor.
A serious solution should use the OTP behaviors (gen_server, supervisor...) as suggested by Steve.
Anyway I wrote a small example module that implement both a server and clients and that can be started on one node using the command erl -sname test for example (or several nodes using erl -sname node1, erl -sname node2...) .
it includes also an example of a shell session that illustrates most of the cases, I hope it can help you to follow the exchanges, synchronous or asynchronous between processes.
NOTE : the access to the user list is not concurrent, it is not possible if the list is owned by a server process like it is in this example. It is why Steve propose to use an ETS to store the information and do real concurrent accesses. I have tried to write the example with interfaces that should allow a quick refactoring with ETS instead of tuple list.
-module(example).
-export([server/0,server_stop/1,server_register_name/2,server_get_address/2, server_quit/2, % server process and its interfaces
client/1,quit/1,register_name/2,get_address/2,send_message/3,print_messages/1, % client process and its interfaces
trace/0]). % to call the tracer for a nice message view
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Client interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
client(Node) ->
% connect the current node to the servernode given in parameter
% it will fail if the connection cannot be established
true = net_kernel:connect_node(Node),
% spawn a client process
spawn(fun () -> client([],unregistered,{server,Node}) end).
register_name(ClientPid,Name) ->
% use a helper to facilitate the trace of everything
send_trace(ClientPid,{register_name,self(),Name}),
% wait for an answer, it is then a synchronous call
receive
% no work needed, simply return any value
M -> M
after 1000 ->
% this introduce a timeout, if no answer is received after 1 second, consider it has failed
no_answer_from_client
end.
get_address(ClientPid,UserName) ->
send_trace(ClientPid,{get_address,self(),UserName}),
% wait for an answer, it is then a synchronous call
receive
% in this case, if the answer is tagged with ok, extract the Value (will be a Pid)
{ok,Value} -> Value;
M -> M
after 1000 ->
no_answer_from_client
end.
send_message(ClientPid,To,Message) ->
% simply send the message, it is asynchronous
send_trace(ClientPid,{send_message,To,Message}).
print_messages(ClientPid) ->
send_trace(ClientPid,print_messages).
quit(ClientPid) ->
send_trace(ClientPid,quit).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% client local functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
client(Messages,Name,Server) ->
receive
{register_name,From,UserName} when Name == unregistered ->
% if not yet registered send the request to the server and
% backward the answer to the requester
Answer = server_register_name(Server,UserName),
send_trace(From,Answer),
NName = case Answer of
registered -> UserName;
_ -> Name
end,
client(Messages,NName,Server);
{register_name,From,_} ->
% if already registered reject the request
send_trace(From,{already_registered_as,Name}),
client(Messages,Name,Server);
{get_address,From,UserName} when Name =/= unregistered ->
Answer = server_get_address(Server,UserName),
send_trace(From,Answer),
client(Messages,Name,Server);
{send_message,To,Message} ->
% directly send the message to the user, the server is not concerned
send_trace(To,{new_message,{erlang:date(),erlang:time(),Name,Message}}),
client(Messages,Name,Server);
print_messages ->
% print all mesages and empty the queue
do_print_messages(Messages),
client([],Name,Server);
quit ->
server_quit(Server,Name);
{new_message,M} ->
% append the new message
client([M|Messages],Name,Server);
_ ->
client(Messages,Name,Server)
end.
do_print_messages(Messages) ->
lists:foreach(fun({D,T,W,M}) -> io:format("from ~p, at ~p on ~p, received ~p~n",[W,T,D,M]) end,Messages).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Server interface
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
server() ->
true = register(server,spawn(fun () -> server([]) end)),
node().
server_stop(Server) ->
send_trace(Server,stop).
server_register_name(Server,User) ->
send_trace(Server,{register_name,self(),User}),
receive
M -> M
after 900 ->
no_answer_from_server
end.
server_get_address(Server,User) ->
send_trace(Server,{get_address,self(),User}),
receive
M -> M
after 900 ->
no_answer_from_server
end.
server_quit(Server,Name) ->
send_trace(Server,{quit,Name}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% server local functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
server(Users) ->
receive
stop ->
ok;
{register_name,From,User} ->
case lists:keyfind(User,1,Users) of
false ->
send_trace(From,registered),
server([{User,From}|Users]);
_ ->
send_trace(From,{already_exist,User}),
server(Users)
end;
{get_address,From,User} ->
case lists:keyfind(User,1,Users) of
false ->
send_trace(From,{does_not_exist,User}),
server(Users);
{User,Pid} ->
send_trace(From,{ok,Pid}),
server(Users)
end;
{quit,Name} ->
server(lists:keydelete(Name,1,Users))
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% global
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
trace() ->
% start a collector, a viewer and trace the "trace_me" ...
et_viewer:start([{trace_global, true}, {trace_pattern, {et,max}},{max_actors,20}]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% helpers
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
send_trace(To,Message) ->
% all messages will be traced by "et"
et:trace_me(50,self(),To,Message,[]),
To ! Message.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% shell commands
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% c(example).
% example:trace().
% N = node().
% C1 = example:client(N).
% example:register_name(pid(0,5555,0),"fails").
% example:register_name(C1,"fails_again").
% example:server().
% example:register_name(C1,"Joe").
% C2 = example:client(N).
% example:register_name(C2,"Bob").
% example:print_messages(C1).
% C2 = example:get_address(C1,"Bob").
% example:send_message(C1,C2,"Hi Bob!").
% example:send_message(C1,C2,"Hi Bob! are you there?").
% example:print_messages(C2).
% example:send_message(C2,C1,"Hi Joe! Got your message.").
% example:print_messages(C2).
% example:print_messages(C1).
% example:quit(C1).
% example:get_address(C2,"Joe").
% example:server_stop({server,N}).
% example:get_address(C2,"Joe").
% example:get_address(C1,"Bob").
here an extract of the event viewer:
I'm very new to Erlang and tried to implement a simple class that has some methods to simulate a database. insert() just inserts a key -> value in the process map, and retrieve() just returns the value from the map. However, I am getting stuck in the loop(). What am I doing wrong?
-module(db).
-export([start/0,stop/0,retrieve/1,insert/2]).
start() ->
register(db, spawn(fun() ->
loop()
end)
),
{started}.
insert(Key, Value) ->
rpc({insert, Key, Value}).
retrieve(Key) ->
rpc({retrieve, Key}).
stop() ->
rpc({stop}).
rpc(Request) ->
db ! {self(), Request},
receive
{db, Reply} ->
Reply
end.
loop() ->
receive
{rpc, {insert, Key, Value}} ->
put(Key, Value),
rpc ! {db, done},
loop();
{rpc, {retrieve, Key}} ->
Val = get(Key),
rpc ! {db, Val},
loop();
{rpc, {stop}} ->
exit(db,ok),
rpc ! {db, stopped}
end.
So, after compiling:
I first call db:start().
and then when trying db:insert("A", 1)., it gets stucked.
Thank you
The problem is in loop/0 function. You're using rpc atom to pattern match the messages received ({rpc, {insert, Key, Value}}), but, as you can see on rpc/1 function, you always send messages with the format {self(), Request} to db process.
self() function returns a PID in the format <X.Y.Z>, which will never match against the atom rpc
For example, let's say you're trying to insert some data using the function insert/2 and self() would return the PID <0.36.0>. When rpc/1 sends the message, on the line db ! {self(), {insert, Key, Value}}, loop/0 will receive {<0.36.0>, {insert, Key, Value}} message, which will never match against {rpc, {insert, Key, Value}}, because rpc is an atom.
The solution is to change rpc atom to a variable, like this:
loop() ->
receive
{Rpc, {insert, Key, Value}} ->
put(Key, Value),
Rpc ! {db, done},
loop();
{Rpc, {retrieve, Key}} ->
Val = get(Key),
Rpc ! {db, Val},
loop();
{Rpc, {stop}} ->
Rpc ! {db, stopped},
exit(whereis(db),ok)
end.
Erlang variables start with capital letters, that's why I used Rpc, instead of rpc.
P.S.: Actually, you had two other problems:
In the last part of loop/0, where you handle stop message, you call exit(db, ok) before you actually answer to rpc. In that case, you'd never receive the {db, stopped} message back from db process, which would be dead by that time. That's why I've changed the order, putting the exit/2 call after Rpc ! {db, stopped}.
When you call exit/2, you were passing db, which is an atom, as the first argument, but exit/2 function expects an PID as first argument, which would raise a badarg error. That's why I've changed it to exit(whereis(db), ok).
Let's walk through this a bit more carefully. What do you mean by "rpc"? "Remote Procedure Call" -- sure. But everything in Erlang is an rpc, so we tend not to use that term. Instead we distinguish between synchronous messages (where the caller blocks, waiting on a response) and aynchronous messages (where the caller just fires off a message and runs off without a care in the world). We tend to use the term "call" for a synch message and "cast" for an asynch message.
We can write that easily, as a call looks a lot like your rpc above, with the added idiom in Erlang of adding a unique reference value to tag the message and monitoring the process we sent a message to just in case it crashes (so we don't get left hanging, waiting for a response that will never come... which we'll touch on in your code in a bit):
% Synchronous handler
call(Proc, Request) ->
Ref = monitor(process, Proc),
Proc ! {self(), Ref, Request},
receive
{Ref, Res} ->
demonitor(Ref, [flush]),
Res;
{'DOWN', Ref, process, Proc, Reason} ->
{fail, Reason}
after 1000 ->
demonitor(Ref, [flush]),
{fail, timeout}
end.
Cast is a bit easier:
cast(Proc, Message) ->
Proc ! Message,
ok.
The definition of call above means that the process we are sending to will receive a message of the form {SenderPID, Reference, Message}. Note that this is different than {sender, reference, message}, as lower-case values are atoms, meaning they are their own values.
When we receive messages we are matching on the shape and values of the message received. That means if I have
receive
{number, X} ->
do_stuff(X)
end
in my code and the process sitting in that receive get a message {blah, 25} it will not match. If it receives another message {number, 26} then it will match, that receive will call do_stuff/1 and the process will continue on. (These two things -- the difference between atoms and Variables and the way matching in receive works -- is why your code is hanging.) The initial message, {blah, 25} will still be in the mailbox, though, at the front of the queue, so the next receive has a chance to match on it. This property of mailboxes is immensely useful sometimes.
But what does a catch-all look like?
Above you are expecting three kinds of messages:
{insert, Key, Value}
{retrieve, Key}
stop
You dressed them up differently, but that's the business end of what you are trying to do. Running the insert message through the call/2 function I wrote above it would wind up looking like this: {From, Ref, {insert, Key, Value}}. So if we expect any response from the process's receive loop we will need to match on that exact form. How do we catch unexpected messages or badly formed ones? At the end of the receive clause we can put a single naked variable to match on anything else:
loop(State) ->
receive
{From, Ref, {insert, Key, Value}} ->
NewState = insert(Key, Value, State),
From ! {Ref, ok},
loop(NewState);
{From, Ref, {retrieve, Key}} ->
Value = retrieve(Key, State),
From ! {Ref, {ok, Value}},
loop(State);
{From, Ref, stop} ->
ok = io:format("~tp: ~tp told me to stop!~n", [self(), From]),
From ! {Ref, shutting_down},
exit(normal)
Unexpected ->
ok = io:format("~tp: Received unexpected message: ~tp~n",
[self(), Unexpected]),
loop(State)
end.
You will notice that I am not using the process dictionary. DO NOT USE THE PROCESS DICTIONARY. This isn't what it is for. You'll overwrite something important. Or drop something important. Or... bleh, just don't do it. Use a dict or map or gb_tree or whatever instead, and pass it through as the process' State variable. This will become a very natural thing for you once you start writing OTP code later on.
Toy around with these things a bit and you will soon be happily spamming your processes to death.
I have the following code:
-module(circle).
-export([proc/1,mother/0,chain/1]).
-spec mother() -> none().
mother() ->
register(mother,self()).
-spec proc(pid()) -> none().
proc(Next) when is_pid(Next) ->
receive
{_, Msg} -> Next ! {self(), Msg}
end.
-spec chain(integer()) -> pid().
chain(0) -> mother;
chain(N) when is_integer(N) ->
spawn(circle,proc,chain(N-1)).
It compiles as expected however whenever I run, it throws a bad argument error when the chain reaches the 0 argument. This is because erlang treats the mother as an atom however I have previously called the mother function which should've registered mother as a pid.
I initially thought the mother does not get registered however after having called this in the console:
-> circle:mother().
-> mother ! {abc}.
abc
I was able to deduce from here that mother does get treated like a Pid. How can I make the code work? How can I make erlang see that mother is a PID?
What I'm trying to achieve is to build N processes in a circle.
Registered process name doesn't turn into pid.
Here is type specification of destination in erlang:send/2 which is the same as ! operator:
dst() = pid()
| port()
| (RegName :: atom())
| {RegName :: atom(), Node :: node()}
As you see send takes many types as destination.
You don't need to care about that, so just remove that guard; make another clause for atom case or append to guard orelse is_atom(Next).
-spec proc(pid() | atom()) -> none().
proc(Next) ->
receive
{_, Msg} -> Next ! {self(), Msg}
end.
You have one error in spawn(circle,proc,chain(N-1)).
spawn/3 takes list of arguments as third argument:
spawn(circle,proc,[chain(N-1)]).
I don't see any benefit from registering a root process. If closing a circle was problematic with pids only, here is how you can do it:
chain(N) ->
chain(N, self()).
chain(0, Mother) -> Mother;
chain(N, Mother) when is_integer(N) ->
spawn(circle,proc,[chain(N-1, Mother)]).
If you want to invoke mother/0 in the chain(0) clause you must correct the clause so that it reads:
chain(0) -> mother();
As it currently stands you are simply returning an atom, since all you have in the body of the function clause is an atom, and not the invocation of a function. Example:
something() -> fun_name. %=> returns `fun_name`
something() -> fun_name(). %=> invokes `fun_name/0` and returns the result of the call.
I have an erlang project that makes a lot of concurrent SOAP requests to my application. Currently, it's limited by how many nodes are available, but I would like to adjust it so that each node can send more than one message at a time.
I've figured that problem out, but I don't know how to get a response back from process running the SOAP request.
This is my function that I'm attempting to use to do multiple threads:
batch(Url, Message, BatchSize) ->
inets:start(),
Threads = for(1, BatchSize, fun() -> spawn(fun() -> attack_thread() end) end),
lists:map(fun(Pid) -> Pid ! {Url, Message, self()} end, Threads).
This function gets called by the person who initiated the stress tester, it is called on every node in our network. It's called continually until all the requested number of SOAP requests have been sent and timed.
This is the attack_thread that is sent the message by the batch method:
attack_thread() ->
receive
{Url, Message, FromPID} ->
{TimeTaken, {ok, {{_, 200, _}, _, _}}} = timer:tc(httpc, request, [post, {Url, [{"connection", "close"}, {"charset", "utf-8"}], "text/xml", Message}, [], []]),
TimeTaken/1000/1000.
end
As you can see, I want it to return the number of seconds the SOAP request took. However, erlang's message passing (Pid ! Message) doesn't return anything useful.
How can I get a result back?
Each of your attack_thread() threads can simply drop a message in the mailbox of the process operating the batch/3 function:
FromPid ! {time_taken, self(), TimeTaken / 1000 / 1000}.
but then you need to collect the results:
batch(Url, Message, BatchSize) ->
inets:start(),
Pids = [spawn_link(fun attack_thread/0) || _ <- lists:seq(1, BatchSize],
[Pid ! {Url, Message, self()} || Pid <- Pids],
collect(Pids).
collect([]) -> [];
collect(Pids) ->
receive
{time_taken, P, Time} ->
[Time | collect(Pids -- [P])]
end.
Some other comments: you probably want spawn_link/1 here. If something dies along the way, you want the whole thing to die. Also, be sure to tune inets httpc a bit so it is more effective. You might also want to look at basho_bench or tsung.
Finally, you can use a closure directly rather than pass the url and message:
attack_thread(Url, Message, From) -> ...
So your spawn is:
Self = self(),
Pids = [spawn_link(fun() -> attack_thread(Url, Message, Self) end) || _ <- ...]
It avoids passing in the message in the beginning.