How to handle dynamically registered processes and pattern matching in Erlang? - erlang

When I register a process to an atom, I can send a message via the atom instead of the Pid interchangeably, which is convenient. However, pattern matching seems to treat Pid and atom as different entities, which is expected but inconvenient. In my example, the {Pid, Response} pattern does not match since Pid in this scope is an atom but the message sent as response contains the actual Pid.
Is there a preferred way to handle this?
The Program:
-module(ctemplate).
-compile(export_all).
start(AnAtom, Fun) ->
Pid = spawn(Fun),
register(AnAtom, Pid).
rpc(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Response} ->
Response;
Any ->
io:format("Finish (wrong):~p~n",[{Pid, Any}])
end.
loop(X) ->
receive
{Sender, Any} ->
io:format("Received: ~p~n",[{Sender, Any}]),
Sender ! {self(), "Thanks for contacting us"},
loop(X)
end.
The Shell:
Eshell V5.10.2 (abort with ^G)
1> c(ctemplate).
{ok,ctemplate}
2> ctemplate:start(foo, fun() -> ctemplate:loop([]) end).
true
3> ctemplate:rpc(foo, ["bar"]).
Received: {<0.32.0>,["bar"]}
Finish (wrong):{foo,{<0.40.0>,"Thanks for contacting us"}}
ok
4> whereis(foo).
<0.40.0>

Use references instead. The example you are suggesting is actually one of the reasons why refs are better for synchronous messages. Another reason is that sometimes you cannot guarantee that the received message is the one you are actually expecting.
So, your code will look like something
rpc(PidOrName, Request) ->
Ref = make_ref(),
PidOrName ! {{self(), Ref}, Request},
receive
{{Pid, Ref}, Response} ->
Response;
Any ->
io:format("Finish (wrong):~p~n",[{PidOrName, Any}])
end.
loop(X) ->
receive
{{Pid, Ref}, Any} ->
io:format("Received: ~p~n",[{Sender, Any}]),
Sender ! {{self(), Ref}, "Thanks for contacting us"},
end,
loop(X).
A couple notes about your and my code:
Note how I moved the last loop/1 call to the end of the function out of the receive block. Erlang does compile-time tail call optimizations, so your code should be fine, but it's a better idea to make tail calls explicitly – helps you to avoid mistakes.
You are probably trying to re-invent gen_server. The only two major differences between gen_server:call/2 and my code above are timeouts (gen_server has them) and that the reference is created by monitoring the remote process. This way, if the process dies before the timeout is thrown, we receive an immediate message. It's slower in many cases, but sometimes proves itself useful.
Overall, try to use OTP and read its code. It's good and gives you better ideas of how Erlang application should work.

rpc(Pid, Request) ->
Pid ! {self(), Request},
receive
{whereis(Pid), Response} ->
Response;
Any ->
io:format("Finish (wrong):~p~n",[{Pid, Any}])
end.
you can this function whereis():
whereis(RegName) -> pid() | port() | undefined
Types:
RegName = atom()
Returns the pid or port identifier with the registered name RegName. Returns undefined if the name is not registered.
For example:
whereis(db).
<0.43.0>

Related

How to implement general Erlang server that can become any kind of specific server

Currently I'm experimenting with Erlang and would like to implement a kind of universal server (like this one) described by Joe Armstrong. The general idea is to create a general server that we can later tell to become a specific one, like this:
universal_server() ->
receive
{become, F} ->
F()
end.
And some specific server:
factorial_server() ->
receive
{From, N} ->
From ! factorial(N),
factorial_server()
end.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
And finally send a "become factorial server" message to the universal server:
test() ->
Pid = spawn(fun universal_server/0),
Pid ! {become, fun factorial_server/0},
Pid ! {self(), 50},
receive
X -> X
end.
What I would like to do is to implement a universal server that can accept multiple subsequent "become" messages (so that I could send a "become factorial server" message and then a "become other kind of specific server" message...).
A naive approach is to require that every specific server implementation will include the {become, F} pattern in a receive clause. Maybe I could have a behavior that defines the general shape of all specific servers (containing the {become, F} clause) and propagates other messages forward to callbacks.
My question is, how to implement such a case in a clean, smart way?
Here is mine:
-module(myserver).
-export([start/0, init/0]).
start() ->
erlang:spawn_link(?MODULE, init, []).
init() ->
State = undefined, % You may want to do something at startup
loop(State).
% if something went wrong comment above line and uncomment below line:
% exit(element(2, catch loop(State))).
loop(MyState) ->
Msg =
receive
Any ->
Any
end,
handle_message(Msg, MyState).
% We got a message for becoming something:
handle_message({become, Mod, InitArgument}, _) ->
% Also our callback may want to do something at startup:
CallbackState = Mod:init(InitArgument),
loop({Mod, CallbackState});
% We got a message and we have a callback:
handle_message(Other, {Mod, CallbackState}) ->
case Mod:handle_message(Other, CallbackState) of
stop ->
loop(undefined);
NewCallbackState ->
loop({Mod, NewCallbackState})
end;
% We got a message and we Don't have a callback:
handle_message(Other, undefined) ->
io:format("Don't have any callback for handling ~p~n", [Other]),
loop(undefined).
Also I wrote a simple counter program for my server:
-module(counter).
-export([init/1, handle_message/2]).
init(Start) ->
Start.
handle_message(inc, Number) ->
Number + 1;
handle_message(dec, Number) ->
Number - 1;
handle_message({From, what_is}, Number) ->
From ! Number;
handle_message(stop, _) ->
stop;
handle_message(Other, Number) ->
io:format("counter got unknown message ~p~n", [Other]),
Number.
Let's test them:
Eshell V10.1 (abort with ^G)
1> S = myserver:start().
<0.79.0>
2> S ! hello.
Don't have any callback for handling hello
hello
3> S ! {become, counter, 10}.
{become,counter,10}
4> S ! hi.
counter got unknown message hi
hi
5> S ! inc.
inc
6> S ! dec.
dec
7> S ! dec.
dec
8> S ! {self(), what_is}.
{<0.77.0>,what_is}
9> flush().
Shell got 9
ok
10> S ! stop.
stop
11> S ! inc.
Don't have any callback for handling inc
inc
What should we do to complete it?
As you can see, It's not a production ready code, We should:
Have a way to set a timeout for initialize.
Have a way to set process spawn options.
Have a way to registering process locally or globally or using custom process registries.
Call callback functions in try catch.
Make sure that a message reply is for current message passing, not for other message that our process sent it before! (what gen module provides as call).
Kill ourself when our starter process died and don't be a zombie process if starter is linked to us!
Call a function at the end for each callback and let them clean those things if they have (you can name it terminate).
Be compatible with OTP sys module, So we should defined its callback functions. see sys callback functions. Then we can turn our process to debug mode, see its I/O, change its state in reloading the code, etc.
Note that proc_lib and gen module can help you to do most of them.

Is there a way to send a closure to remote node in erlang?

It seams erlang only sends fun references to remote nodes. when trying to send closure, it apparently inlines closure in the calling module and sends a fun ref to that inlined fun to remote node. Here's the test:
-module(funserver).
-compile(export_all).
loop()->
receive {From, ping} ->
error_logger:info_msg("received ping from ~p~n", [From]),
From ! pong,
loop();
{From, Fun} when is_function(Fun) ->
error_logger:info_msg("executing function ~p received from ~p~n", [Fun, From]),
From ! Fun(),
loop();
M ->
error_logger:error_msg("received ~p, don't know what to do with it", [M])
end.
and the test on the client node:
test_remote_node_can_execute_sent_clojure()->
{ok, ModName, Binary} = compile:file(funserver, [verbose,report_errors,report_warnings, binary]),
{module, ModName} = rpc:call(?other_node, code, load_binary, [ModName, atom_to_list(ModName), Binary]),
Pid = spawn(?other_node, funserver, loop, []),
OutVar = {"token with love from", node()},
Pid ! {self(), fun()-> {OutVar, erlang:node()} end},
receive Result ->
Result = {OutVar, node(Pid)}
after 300 ->
timeout
end.
getting
Error in process <7162.123.0> on node servas#sharas with exit value:
{undef,[{#Fun<tests.1.127565388>,[],[]},
{funserver,loop,0,[{file,"funserver.erl"},{line,12}]}]}
timeout
So can clojure be sent to remote node?
The problem with your example is that the client node compiles and sends the funserver module to the remote node - but that module is already there and executing, waiting to receive a message - but it doesn't send the tests module, which is the module that actually contains the fun you're sending across.
In the compile:file line, change funserver to tests, and it should work.
Also, you could use code:get_object_code instead of compile:file, since the module is already compiled and loaded in the local node.

When to use timeouts and monitors in Erlang?

The Learn You Some Erlang book has the below code. Why does the book sometimes use just timeouts and sometimes use both monitors and timeouts? In the below what is the need for the monitor since the timeout will effectively detect if the process is down?
%% Synchronous call
order_cat(Pid, Name, Color, Description) ->
Ref = erlang:monitor(process, Pid),
Pid ! {self(), Ref, {order, Name, Color, Description}},
receive
{Ref, Cat} ->
erlang:demonitor(Ref, [flush]),
Cat;
{'DOWN', Ref, process, Pid, Reason} ->
erlang:error(Reason)
after 5000 ->
erlang:error(timeout)
end.
Also compare the following where add_event doesn't use a monitor but subscribe does
subscribe(Pid) ->
Ref = erlang:monitor(process, whereis(?MODULE)),
?MODULE ! {self(), Ref, {subscribe, Pid}},
receive
{Ref, ok} ->
{ok, Ref};
{'DOWN', Ref, process, _Pid, Reason} ->
{error, Reason}
after 5000 ->
{error, timeout}
end.
add_event(Name, Description, TimeOut) ->
Ref = make_ref(),
?MODULE ! {self(), Ref, {add, Name, Description, TimeOut}},
receive
{Ref, Msg} -> Msg
after 5000 ->
{error, timeout}
end.
There is a big difference between those two examples.
order_cat(Pid, Name, Color, Description) uses a monitor for the duration of one request, and calls erlang:demonitor/2 after receiving a successful response.
subscribe(Pid) establishes a monitor more permanently. The intent is that the event client would receive the {'DOWN', Ref, process, Pid, Reason} message in its main receive block and handle the fact that the event server died. Note how subscribe(Pid) returns the monitor reference as {ok, Ref} for the client to use for this purpose. Unfortunately, the book doesn't show what the event client would look like.
Now as to the more general question about monitors vs. timeouts: The disadvantage of monitoring is a small additional cost, and a small amount of complexity. The advantages compared to timeouts include:
If the target process doesn't exist or crashes trying to handle the message you just sent, you find out immediately rather than after a timeout. For some applications, that time saved is very important.
If the target process crashes, the Reason part of the monitor message tells you something about why. In some applications, the client may try different recovery actions depending on the reason.
The sending process knows whether to expect a response from the target process in the future. In the case of a timeout, if the target process was merely slow to respond, the sending process will get a response in its mailbox. If the sender fails to clear such responses from its mailbox, it will become slow and eventually terminate. The book touches on this problem in the Selective Receive section. The gen_server:call/3 documentation also explains this issue briefly.
The implementation of gen_server:call/2 uses a monitor. Since this is how many production erlang requests are sent, you can trust that it is well optimized and the recommended default. In fact, you should be using OTP rather than rolling your own.
See the source code here. Here's the relevant function:
do_call(Process, Label, Request, Timeout) ->
try erlang:monitor(process, Process) of
Mref ->
%% If the monitor/2 call failed to set up a connection to a
%% remote node, we don't want the '!' operator to attempt
%% to set up the connection again. (If the monitor/2 call
%% failed due to an expired timeout, '!' too would probably
%% have to wait for the timeout to expire.) Therefore,
%% use erlang:send/3 with the 'noconnect' option so that it
%% will fail immediately if there is no connection to the
%% remote node.
catch erlang:send(Process, {Label, {self(), Mref}, Request},
[noconnect]),
receive
{Mref, Reply} ->
erlang:demonitor(Mref, [flush]),
{ok, Reply};
{'DOWN', Mref, _, _, noconnection} ->
Node = get_node(Process),
exit({nodedown, Node});
{'DOWN', Mref, _, _, Reason} ->
exit(Reason)
after Timeout ->
erlang:demonitor(Mref, [flush]),
exit(timeout)
end
catch
error:_ ->
%% Node (C/Java?) is not supporting the monitor.
%% The other possible case -- this node is not distributed
%% -- should have been handled earlier.
%% Do the best possible with monitor_node/2.
%% This code may hang indefinitely if the Process
%% does not exist. It is only used for featureweak remote nodes.
Node = get_node(Process),
monitor_node(Node, true),
receive
{nodedown, Node} ->
monitor_node(Node, false),
exit({nodedown, Node})
after 0 ->
Tag = make_ref(),
Process ! {Label, {self(), Tag}, Request},
wait_resp(Node, Tag, Timeout)
end
end.
Why does the book sometimes use just timeouts and sometimes use both monitors and timeouts?
The process may terminate before the timeout expires, but you don’t want to wait longer for no reason. You want the call to take at most five seconds, not always five seconds.
In the below what is the need for the monitor since the timeout will effectively detect if the process is down?
It won’t; terminations don’t trigger timeouts, time does.

Does Erlang's open_port call link to resulting process?

I've got some C code that I'm executing as an external process using Erlang's Port capability. I want the process that starts the C code, via open_port, to detect if the C code crashes. The documentation isn't completely clear to me, but as I understand it, a two-way link is established between the Erlang process and the external code. If one dies, the other is notified.
Here is a slightly modified version of the code in the "Erlang Interoperability Tutorial Guide" (http://www.erlang.org/doc/tutorial/c_port.html):
init(ExtPrg) ->
register(cport, self()),
process_flag(trap_exit, true),
Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
PInfo = erlang:port_info(Port),
io:format("Port: ~p PInfo: ~p~n", [Port, PInfo]),
RVal = link(Port),
io:format("link? ~p~n", [RVal]),
loop(Port).
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {cport, decode(Data)}
end,
loop(Port);
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} ->
exit(port_terminated)
end.
The init call correctly executes the C code, and as you can see sets trap_exit, but the EXTT message is not received when I kill the C code using kill -HUP from the Unix shell. I've tried with and without the link call (the Erlang documentation does not use it). The printing code I've added generates:
Eshell V5.9.1 (abort with ^G)
1> cport:start("./cport 22").
Port: #Port<0.630> PInfo: [{name,"./cport 22"},
{links,[<0.38.0>]},
{id,630},
{connected,<0.38.0>},
{input,0},
{output,0}]
<0.38.0>
link? true
It appears that a link is registered but I'm not catching the trap. What am I missing?
Try the extra option exit_status when calling open_port().
I did a similar Erlang program for supervising game servers.
When the external game server crashed I wanted to restart it from my central Erlang monitoring system.
This was the code that worked for me:
erlang:open_port({spawn_executable, "<Path to my game server start script>"},
[ exit_status ]),
When the external process is killed you will get a message of type
{Port,{exit_status,Status}}

Erlang: Why can't I register self()?

This line fails with a badarg exception:
register(myproc, self()),
The documentation says that self/0 returns a pid and that register/2 takes a pid. So what gives?
Edit: No, seriously, it's not already registered, it's not a reserved atom, and it works when I register it from the process that's spawning it.
Oh weird! Okay, I got some more clues. When I move the call to register() around to different places, sometimes it works and sometimes it breaks. Here's my sample code. Run it before you call me crazy. :-)
-module(pingpong).
-export([start/1, ping/2, pong/0]).
ping(N, Pong_Pid) ->
link(Pong_Pid),
pingr(N, Pong_Pid).
pingr(0, _) ->
io:format("Ping exiting~n", []),
exit(ping);
pingr(N, Pong_Pid) ->
Pong_Pid ! {ping, self()},
receive
pong ->
io:format("Ping received pong~n", [])
end,
pingr(N - 1, Pong_Pid).
pong() ->
%% This one works.
%%register(pong, self()),
process_flag(trap_exit, true),
pongr().
pongr() ->
%% This one fails.
register(pong, self()),
receive
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pongr();
{'EXIT', From, Reason} ->
io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
end.
start(Ping_Node) ->
PongPID = spawn(pingpong, pong, []),
spawn(Ping_Node, pingpong, ping, [3, PongPID]).
If the process is already registered, it will throw a badarg. There is also some other cases that causes this, like the name is already used. See the erlang:register/2 docs for more.
EDIT
It's great that you posted code to reproduce your problem.
So, the first time you enter pongr/0 you will register self(). When you receive a message, you will process it and call pongr/0 again. The second time you enter pongr/0 you try to register self(), which fails because it's already registered.
Also, if you want to use register a large number of processes, you should look into gproc. register/2 requires an atom as the key and there is a limit of around one million atoms, unless you explicitly change it. See the efficiency guide. gproc can also run distributed and may thus be used instead of the global module.
is myproc already registered?
first call should succeed, additional calls will cause badarg exception.
1> register(myproc, self()).
true
2> myproc ! foo.
foo
3> flush().
Shell got foo
ok
4> register(myproc, self()).
** exception error: bad argument
in function register/2
called as register(myproc,<0.30.0>)

Resources