Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

Lecture 6

Erlang
Linking processes Error handling Fault-tolerant systems Distributed Programming

Linking Processes
If a process in some way depends on another, then it may well want to keep an eye on the health of that second process. Linking:

2013 DEI-ISEP

Jorge Coelho

Linking Processes
What happens when a process receives an exit signal? If the receiver hasnt taken any special steps, the exit signal will cause it, too, to exit. However, a process can ask to trap these exit signals. When a process is in this state, it is called a system process. If a process linked to a system process exits for some reason, the system process is not automatically terminated. Instead, the system process receives an exit signal, which it can trap and process.
2013 DEI-ISEP Jorge Coelho 3

on_exit Handler
on_exit(Pid, Fun) -> spawn(fun() -> process_flag(trap_exit, true), link(Pid), receive {'EXIT', Pid, Why} -> Fun(Why) end end).

2013 DEI-ISEP

Jorge Coelho

Example
1> F = fun() -> receive X -> list_to_atom(X) end end. #Fun<erl_eval.20.69967518> 2> Pid = spawn(F). <0.61.0> 3> our_module:on_exit(Pid, fun(Why) -> io:format(" ~p died with:~p~n",[Pid, Why]) end). <0.63.0> 4> Pid ! hello. hello <0.61.0> died with:{badarg,[{erlang,list_to_atom,[hello]}]}
2013 DEI-ISEP Jorge Coelho 5

Details of Error Handling


Links A link is something that defines an error propagation path between two processes. If two processes are linked together and one of the processes dies, then an exit signal will be sent to the other process.

2013 DEI-ISEP

Jorge Coelho

Details of Error Handling


Exit signals An exit signal is something generated by a process when the process dies. This signal is broadcast to all processes that are in the link set of the dying process. The exit signal contains an argument giving the reason why the process died.

2013 DEI-ISEP

Jorge Coelho

Details of Error Handling


Exit signals (continued) This reason can be set explicitly by calling the primitive exit(Reason), or it is set implicitly when an error occurs. For example, if a program tries to divide a number by zero, then the exit reason will be badarith. When a process has successfully evaluated the function it was spawned with, it will die with the exit reason normal. A process Pid1 can explicitly send an exit signal X to a process Pid2 by evaluating exit(Pid2, X). The process that sends the exit signal does not die; it resumes execution after it has sent the signal. Pid2 will receive a {EXIT, Pid1, X} message (if it is trapping exits), exactly as if the originating process had died. Using this mechanism, Pid1 can fake its own death (this is deliberate).
2013 DEI-ISEP Jorge Coelho 8

Details of Error Handling


System processes When a process receives a non-normal exit signal, it too will die unless it is a special kind of process called a system process. When a system process receives an exit signal Why from a process Pid, then the exit signal is converted to the message {EXIT, Pid, Why} and added to the mailbox of the system process. Calling the BIF process_flag(trap_exit, true) turns a normal process into a system process that can trap exits.
2013 DEI-ISEP Jorge Coelho 9

Exit signal arrives at a process


trap_exit Exit Signal true true false false false kill X normal kill X Action Die: Broadcast the exit signal killed to the link set. Add {EXIT, Pid, X} to the mailbox. Continue: Do-nothing signal vanishes. Die: Broadcast the exit signal killed to the link set. Die: Broadcast the exit signal X to the link set.

Attention: kill should be used carefully!


2013 DEI-ISEP Jorge Coelho 10

Trapping Exits
Option 1: I Dont Care If a Process I Create Crashes

Pid = spawn(fun() -> ... end)

2013 DEI-ISEP

Jorge Coelho

11

Trapping Exits
Option 2: I Want to Die If a Process I Create Crashes

Pid = spawn_link(fun() -> ... end)

2013 DEI-ISEP

Jorge Coelho

12

Trapping Exits
Option 3: I Want to Handle Errors If a Process I Create Crashes
... process_flag(trap_exit, true), Pid = spawn_link(fun() -> ... end), ... loop(...). loop(State) -> receive {'EXIT', SomePid, Reason} -> %% do something with the error loop(State1); ... end
2013 DEI-ISEP Jorge Coelho 13

How Can We Make a Fault-Tolerant System in Erlang?


One process does the job, and another process watches the first process and takes over if things go wrong. Thats why we need to monitor processes and to know why things fail. In distributed Erlang, the process that does the job and the processes that monitor the process that does the job can be placed on physically different machines. Using this technique, we can start designing faulttolerant software. The basic language primitive that makes all this possible is the link primitive.

2013 DEI-ISEP

Jorge Coelho

14

Keep-Alive Process
Make a registered process that is always alive. If it dies for any reason,it will be immediately restarted.

keep_alive(Name, Fun) -> register(Name, Pid = spawn(Fun)), on_exit(Pid, fun() -> keep_alive(Name, Fun) end).

2013 DEI-ISEP

Jorge Coelho

15

Distributed Programming
There are a number of reasons why we might want to write distributed applications. Here are some: Performance - We can make our programs go faster by arranging that different parts of the program are run in parallel on different machines. Reliability - We can make fault-tolerant systems by structuring the system to run on several machines. If one machine fails, we can continue on another machine. Scalability - As we scale up an application, sooner or later we will exhaust the capabilities of even the most powerful machine. At this stage we have to add more machines to add capacity. Adding a new machine should be a simple operation that does not require large changes to the application architecture.
2013 DEI-ISEP Jorge Coelho 16

Name Server
start() -> register(kvs, spawn(fun() -> loops() end)). store(Key, Value) -> rpc({store, Key, Value}). lookup(Key) -> rpc({lookup, Key}). rpc(Q) -> kvs ! {self(), Q}, receive {kvs, Reply} -> Reply end. loops() -> receive {From, {store, Key, Value}} -> put(Key, {ok, Value}), From ! {kvs, true}, loops(); {From, {lookup, Key}} -> From ! {kvs, get(Key)}, loops() end.
2013 DEI-ISEP Jorge Coelho 17

Name Server
1> kvs:store({location,isep},"Porto"). true 2> kvs:store(weather,raining). true 3> kvs:lookup(raining). undefined 4> kvs:lookup(weather). {ok,raining} 5> kvs:lookup({location,isep}). {ok,"Porto"}
2013 DEI-ISEP Jorge Coelho 18

Same Host and Different Nodes


We start two different Erlang nodes:
erl sname mickey erl sname goofy

Server started in node mickey:


(mickey@lucas)3> kvs:start().

2013 DEI-ISEP

Jorge Coelho

19

Same Host and Different Nodes


kvs functions called in goofy:
(goofy@lucas)6> rpc:call(mickey@lucas,kvs,store,[{location,isep},Porto"]). True (goofy@lucas)7> rpc:call(mickey@lucas,kvs,lookup,[{location,isep}]). {ok,Porto"}

Note: this rpc function is a builtin function of Erlang.

We can swap back to mickey and check the value of the {location,isep}:
(mickey@lucas)6> kvs:lookup({location,isep}). {ok,Porto"}
2013 DEI-ISEP Jorge Coelho 20

10

Different Hosts
Start Erlang with the -name parameter. When we have two nodes on the same machine, we use short names (as indicated by the -sname flag), but if they are on different networks, we use -name. We can also use -sname on two different machines when they are on the same subnet. Using -sname is also the only method that will work if no DNS service is available. Ensure that both nodes have the same cookie. Both nodes must be started with the same command-line argument setcookie. Make sure the fully qualified hostnames of the nodes concerned are resolvable by DNS. Make sure that both systems have the same version of the code that we want to run and the same version of Erlang.
2013 DEI-ISEP Jorge Coelho 21

Different Hosts
To connect two different hosts in the Internet:
Make sure that port 4369 is open for both TCP and UDP traffic. This port is used by a program called epmd (short for the Erlang Port Mapper Daemon). Choose a port or range of ports to be used for distributed Erlang, and make sure these ports are open. If these ports are Min and Max, then start Erlang with the following command:
werl -name ... -setcookie ... -kernel inet_dist_listen_min Min \ inet_dist_listen_max Max

2013 DEI-ISEP

Jorge Coelho

22

11

ETS and DETS


ETS (Erlang Term Storage) and DETS (Disk ETS) perform basically the same task: they provide large key-value lookup tables. ETS is memory resident, while DETS is disk resident. ETS is highly efficientusing ETS, you can store colossal amounts of data (if you have enough memory) and perform lookups in constant (or in some cases logarithmic) time. Because DETS uses disk storage, it is far slower than ETS but will have a much smaller memory footprint when running. ETS and DETS tables can be shared by several processes, making interprocess access to common data highly efficient. ETS and DETS tables are data structures for associating keys with values.
2013 DEI-ISEP Jorge Coelho 23

A Server with Hot Code Swapping


-module(server). -export([start/2, rpc/2, swap_code/2]). start(Name, Mod) -> register(Name,spawn(fun() -> loop(Name,Mod,Mod:init()) end)). swap_code(Name, Mod) -> rpc(Name, {swap_code, Mod}). rpc(Name, Request) -> Name ! {self(), Request}, receive {Name, Response} -> Response end. loop(Name, Mod, OldState) -> receive {From, {swap_code, NewCallBackMod}} -> From ! {Name, ack}, loop(Name, NewCallBackMod, OldState); {From, Request} -> {Response, NewState} = Mod:handle(Request, OldState), From ! {Name, Response}, loop(Name, Mod, NewState) end.
2013 DEI-ISEP Jorge Coelho 24

12

A Server with Hot Code Swapping


If we send the server a swap code message, then it will change the callback module to the new module contained in the message.
-module(name_server). -export([init/0, add/2, whereis/1, handle/2]). -import(server, [rpc/2]). %% client routines add(Name, Place) -> rpc(our_server, {add, Name, Place}). whereis(Name) -> rpc(our_server, {whereis, Name}). %% callback routines init() -> dict:new(). handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)}; handle({whereis, Name}, Dict) -> {dict:find(Name, Dict), Dict}.
2013 DEI-ISEP Jorge Coelho 25

A Server with Hot Code Swapping


First well start server with the name_server callback module:
1> server:start(our_server, name_server). true 2> name_server:add(joe, "at home"). ok 3> name_server:add(helen, "at work"). ok

2013 DEI-ISEP

Jorge Coelho

26

13

A Server with Hot Code Swapping


Now suppose we want to find all the names. There is no function in our API that can do this.
-module(new_name_server). -export([init/0, add/2, all_names/0, delete/1, whereis/1, handle/2]). -import(server, [rpc/2]). %% interface all_names() -> rpc(our_server, allNames). add(Name, Place) -> rpc(our_server, {add, Name, Place}). delete(Name) -> rpc(our_server, {delete, Name}). whereis(Name) -> rpc(our_server, {whereis, Name}). %% callback routines init() -> dict:new(). handle({add, Name, Place}, Dict) -> {ok, dict:store(Name, Place, Dict)}; handle(allNames, Dict) -> {dict:fetch_keys(Dict), Dict}; handle({delete, Name}, Dict) -> {ok, dict:erase(Name, Dict)}; handle({whereis, Name}, Dict) -> {dict:find(Name, Dict), Dict}.
2013 DEI-ISEP Jorge Coelho 27

A Server with Hot Code Swapping


Well replace name_server with the new_name_server callback module:
4> c(new_name_server). {ok,new_name_server} 5> server:swap_code(our_server, new_name_server). ack 6> new_name_server:all_names(). [joe,helen]

2013 DEI-ISEP

Jorge Coelho

28

14

Even more fun


-module(server). -export([start/0, rpc/2]). start() -> spawn(fun() -> wait() end). wait() -> receive {become, F} -> F() end. rpc(Pid, Q) -> Pid ! {self(), Q}, receive {Pid, Reply} -> Reply end.

This server does nothing...


2013 DEI-ISEP Jorge Coelho 29

Even more fun


-module(my_fac_server). -export([loop/0]). loop() -> receive {From, {fac, N}} -> From ! {self(), fac(N)}, loop(); {become, Something} -> Something() end. fac(0) -> 1; fac(N) -> N * fac(N-1).

This one calculates the factorial of a number...


2013 DEI-ISEP Jorge Coelho 30

15

Even more fun


We just implemented a server capable of changing himself!
2> c(my_fac_server). {ok,my_fac_server} 3> Pid ! {become, fun my_fac_server:loop/0}. {become,#Fun<my_fac_server.loop.0>} 4> server:rpc(Pid, {fac,30}). 265252859812191058636308480000000

2013 DEI-ISEP

Jorge Coelho

31

16

You might also like