2
votes

I am an erlang newbie and I am trying to build my first messaging application. My question has 2 parts

When I open two separate erlang consoles on my terminal without specifying a -sname attribute how do both erlang consoles have the same process pids. Are they actually the same process, How?


Terminal #1 
--------------

$> erl 

Erlang R15B01 (erts-5.9.1) [source] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.1  (abort with ^G)
1> self().
0.31.0

Similarly for terminal 2


Erlang R15B01 (erts-5.9.1) [source] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.9.1  (abort with ^G)
1> self().
0.31.0

How can both be running on the same process. Are these erlang processes and not native processes?

If I am building a messaging application and I need some way to track the information of the message that is being sent by each user. The basic design I've tried to use is create a new chat client for each user. I spawn a process to keep track of the person who receives the message and thus store his nickname there.

Similarly i created another module that is used for keeping track of who sends the messages but in this case i used the self() Pid to keep track of senders. This takes place in the chat_client_sender module.


-module(chat_client).
-export([start/0, stop/0, loop/1, login/3, logout/1, send_message/2]).
-define(SERVER, chat_client).

start() ->
    message_router:start(),
    chat_client_sender:start().

stop() ->
    message_router:stop(),
    chat_client_sender:stop().

login(Uid, Password, Nickname) ->
    io:format("~p My Pid", [self()]),
    Pid = spawn(chat_client, loop, [Nickname]),     
    case message_router:login(Uid, Password, Nickname, Pid) of
        {ok, logged_in} ->
            chat_client_sender:add_sender(self(), {Uid, Nickname}),
            {ok, logged_in};
        {error, invalid_uid_or_pwd} ->
            {error, invalid}
    end.

logout(Uid) ->
    case message_router:logout(Uid) of
        {ok, logged_out} ->
            {ok, logged_out};       
        ignored ->
            ignored;
        _Someshit ->
            io:format("Some Shit ~p", _Someshit)
    end.

send_message(ToUid, MessageBody) ->
    chat_client_sender:send_message(ToUid, MessageBody).


loop(Nickname) ->
    receive
        {print_msg, Messagebody, SenderNickname} ->
            io:format("~p: ~p to ~p~n", [SenderNickname, Messagebody, Nickname]),
            loop(Nickname);     
        stop -> 
            ok
    end.    


The chat_client_sender module


-module(chat_client_sender).
-export([start/0, stop/0, add_sender/2, loop/1, get_sender/1, send_message/2]).
-define(SERVER, chat_client_sender).


start() ->
    erlang:register(?SERVER, spawn(chat_client_sender, loop, [dict:new()])).

stop() ->
    ?SERVER ! stop.

add_sender(SenderPid, {Uid, Nickname}) ->
    io:format("Adding Sender ~p ~p ~p ~n", [SenderPid, Uid, Nickname]),
    ?SERVER ! {add_sender, SenderPid, {Uid, Nickname}}.

get_sender(SenderPid) ->
    ?SERVER ! {get_sender, SenderPid}.

send_message(ToUid, MessageBody) ->
    ?SERVER ! {send_msg, ToUid, MessageBody}.

loop(MemberPids) ->
    receive
        {add_sender, SenderPid, {Uid, Nickname}} ->
            case dict:find(SenderPid, MemberPids) of
                {ok, {Uid, Nickname}} ->    
                    io:format("Pid exists ~n"),             
                    loop(MemberPids);
                error ->
                    loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids))
            end;
        {send_msg, ToUid, MessageBody, SenderPid} ->
            case get_sender(SenderPid, MemberPids) of
                {found, _Uid, Nickname} ->
                    message_router:send_message(ToUid, MessageBody, Nickname),
                    loop(MemberPids);
                not_found ->
                    not_found,
                    loop(MemberPids)
            end;
        {remove_sender, SenderPid} ->
            case get_sender(SenderPid, MemberPids) of
                {found, _Uid, _Nickname} ->
                    loop(dict:erase(SenderPid, MemberPids));
                not_found ->
                    ignored,
                    loop(MemberPids)
            end;
        {get_sender, SenderPid} ->
            case get_sender(SenderPid, MemberPids) of
                Any -> 
                    io:format("GET SENDER ~p~n", [Any])
            end,
            loop(MemberPids);
        stop ->
            ok
    end.


get_sender(SenderPid, MemberPids) ->
    case dict:find(SenderPid, MemberPids) of 
        {ok, {Uid, Nickname}} ->
            {found, {Uid, Nickname}};
        error ->
            not_found
    end.


So my application begins to fail at add_sender clause in my loop method which stores the SenderPid arriving from the chat_client.

Here is an example


 chat_client:start().
true
9> chat_client_sender:add_sender(self(), {'sid@abc.com', 'sid'}).
Adding Sender  'sid@abc.com' sid 
{add_sender,,{'sid@abc.com',sid}}
10> chat_client_sender:add_sender(self(), {'sid1@abc.com', 'sid1'}).
Adding Sender  'sid1@abc.com' sid1 
{add_sender,,{'sid1@abc.com',sid1}}
11> 
=ERROR REPORT==== 13-Oct-2012::19:12:42 ===
Error in process  with exit value: {{case_clause,{ok,{'sid@abc.com',sid}}},[{chat_client_sender,loop,1,[{file,"chat_client_sender.erl"},{line,25}]}]} 

As per my understanding it should simply continue with the tail recursion on the {ok, {Uid, Nickname}} clause in the chat_client_sender module.


...
    receive
        {add_sender, SenderPid, {Uid, Nickname}} ->
            case dict:find(SenderPid, MemberPids) of
                {ok, {Uid, Nickname}} ->    
                    io:format("Pid exists ~n"),             
                    loop(MemberPids);
                error ->
                    loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids))
            end;
...

I would really appreciate some help understanding whats going on here. Also I would really appreciate it if you could review my code and tell me more about best practices and things that I could do better. My code is available at goo.gl/yY4kR

Thank you

2

2 Answers

6
votes

First question:

The first part of the PID denotes the erlang node in which the process runs with 0 meaning that the the process is running in the local node. Now, the PID you got is the PID of the shell which is just erlang process; for example, it can die (and respawn automatically):

1> self().
<0.32.0>
2> [X] = [4,2].
** exception error: no match of right hand side value [4,2]
3> self().     
<0.35.0>

Second question:

The error is located here (btw finding the minimum part of the code that is problematic is very useful, not only if you are doing the debugging yourself but also when you post a question - usually more people try to help when the code you need to study is small. also, as far as I know, SO is doesn't really focus on code reviewing):

loop(MemberPids) ->
    receive
        {add_sender, SenderPid, {Uid, Nickname}} ->
            case dict:find(SenderPid, MemberPids) of
                {ok, {Uid, Nickname}} ->    
                    io:format("Pid exists ~n"),             
                    loop(MemberPids);
                error ->
                    loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids))
            end
    end.

So, loop/1 receives one kind of message, of the form {add_sender, SPID, {UID, Nick}}
When it receives such a message it searches on the dictionary for a record having as key the value SPID The dictionary responds either with error or a touple of the form {ok, {UID2, Nick2}}; you use case to separate those.
However, when you try to match {ok, {UID2, Nick2}} you don't use new variables; you use the old, already instantiated variables {UID, Nick} and therefore, if those are different, it will fail (since no clause of case matches).

So what can you do? Since you don't use them anyway you can just replace them with underscores:

loop(MemberPids) ->
    receive
        {add_sender, SenderPid, {Uid, Nickname}} ->
            case dict:find(SenderPid, MemberPids) of
                {ok, {_, _}} ->    
                    io:format("Pid exists ~n"),             
                    loop(MemberPids);
                error ->
                    loop(dict:store(SenderPid, {Uid, Nickname}, MemberPids))
            end
    end.

Btw, since you never use Uid or Nickname separately you can just replace the tupple {Uid,Nickname} with a variable:

loop(MemberPids) ->
    receive
        {add_sender, SenderPid, Value} ->
            case dict:find(SenderPid, MemberPids) of
                {ok, _} ->    
                    io:format("Pid exists ~n"),             
                    loop(MemberPids);
                error ->
                    loop(dict:store(SenderPid, Value, MemberPids))
            end
    end.

Well, it's not completely equivalent since the first version will fail if the dictionary returns a value like {ok, {a,b,c}} but I dont really see the point of this check.

Furthermore, it's better (at least semantically) to use dict:is_key/2 since you just want to check if the new element already exists. It can also be faster, depending on the implementation, since it just has to see if the key exists and not retrieve the value; however, I haven't tested it so perhaps there isn't such a optimization. Besides, it will probably make no difference for a small scale project.

But note that there is a special case: what if you try to add someone with the same PID and same value (essentially re-register the same user). The current implementations still give the same error. Depending on your specifications you might want to change that.

Have fun!

4
votes

In answer to your first question: erlang processes are not native processes but "internal" processes. So when running two erlang nodes each one has its own set of processes. In both cases the shell process in which you call self() has the same pid (process identifier).

There is no implicit or built-in way of determining who sent a message. The standard way is to do as in your example and explicitly have the message contain the sender. We found that this was the best way.