Advanced Programming – Erlang Introduction — The Sequel
Advanced Programming
Erlang Introduction — The Sequel
Ken Friis Larsen
kflarsen@diku.dk
Department of Computer Science
University of Copenhagen
October 4, 2018
1 / 27
Today’s Menu
I Recap
I Designing Servers
I Background: Registering processes
I Background: Exceptions again
I Linking processes
I Library code for making robust servers
2 / 27
Recap
I Organise your code in modules
I Make sure that you understand the basic concurrency
primitives in Erlang
I Let’s review the primitives by making a parallel version of fib
(using the concurrency primitives).
3 / 27
Dealing With State
I Functions are pure (stateless).
I Processes are stateful.
I We organise our code as micro-servers that manage a state
which can be manipulated via a client API.
I Functions starts processes, processes runs functions, functions
are defined in modules.
4 / 27
Client–Server Basic Request-Response
I A server is process that loops (potentially) forever.
I Clients communicate through a given API/Protocol
I That is, we start with the following template:
start() -> spawn(fun () -> loop(Initial) end).
request_reply(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Response} -> Response
end.
loop(State) ->
receive
{From, Request} ->
{NewState, Res} = ComputeResult(Request, State),
From ! {self(), Res},
loop(NewState)
end.
5 / 27
Example: Position Server
start(Start) -> spawn(fun () -> loop(Start) end).
move(Pid, Dir) -> request_reply(Pid, {move, Dir}).
get_pos(Pid) -> request_reply(Pid, get_pos).
request_reply(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Response} -> Response
end.
loop({X,Y}) ->
receive
{From, {move, north}} ->
From ! {self(), ok},
loop({X, Y+1});
{From, {move, west}} ->
From ! {self(), ok},
loop({X-1, Y});
{From, {get_pos}} ->
From ! {self(), {X,Y}},
loop({X,Y})
end.
6 / 27
Student exercise: Count Server
I Let’s make a server that can keep track of a counter
I What is the client API?
I What is the internal state?
7 / 27
Example: Phone-Book, Interface
start() -> spawn(fun() -> loop(#{}) end).
add(Pid, Contact) ->
request_reply(Pid, {add, Contact}).
list_all(Pid) ->
request_reply(Pid, list_all).
update(Pid, Contact) ->
request_reply(Pid, {update, Contact}).
8 / 27
Example: Phone-Book, Impletemtation 1
loop(Contacts) ->
receive
{From, {add, Contact}} ->
{Name,_,_} = Contact,
case maps:is_key(Name, Contacts) of
false ->
From ! {self(), ok},
loop(Contacts#{Name => Contact});
true ->
From ! {self(), {error, Name, is_already_there}},
loop(Contacts)
end;
9 / 27
Example: Phone-Book, Implementation 2
{From, list_all} ->
List = maps:to_list(Contacts),
From ! {self(),
{ok, lists:map(fun({_, C}) -> C end, List)}},
loop(Contacts);
{From, {update, Contact}} ->
{Name,_,_} = Contact,
NewContacts = maps:remove(Name, Contacts),
From ! {self(), ok},
loop(maps:put(Name, Contact, NewContacts));
{From, Other} ->
From ! {self(), {error,unknow_request, Other}},
loop(Contacts)
end.
10 / 27
Communication Patterns
I Synchronous (aka Blocking), like the simple Request-Response
function blocking (aka request_reply).
blocking(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Response} -> Response
end.
I Asynchronous (aka Non-Blocking), standard sending of
messages in Erlang
async(Pid, Msg) ->
Pid ! Msg.
11 / 27
Design “Method”
I Determine the API:
I names
I types
I blocking or non-blocking
I Design internal protocols
I Split into servers (processes)
12 / 27
Background: Registering Processes
I It can be convenient to register a process under a global name,
so that we can easily get it without threading a pid around.
I register(Name, Pid) registers the process with Pid under
Name
I whereis(Name) gives us the pid registered for Name; or
undefined if Name is not registered
I Name ! Mesg sends Mesg to the process registered under Name.
13 / 27
Background: Exceptions Revisited
Exceptions comes in different flavours:
try Expr of
Pat1 -> Expr1;
Pat2 -> Expr2;
…
catch
ExType1: ExPat1 -> ExExpr1;
ExType2: ExPat2 -> ExExpr2;
…
after
AfterExpr
end
Where ExType is either throw, exit, or error (throw is the default).
14 / 27
Background: Generating Exceptions
I We can generate all kind of exceptions:
I throw(Why) to throw an exception that the caller might want to
catch. For normal exceptions.
I exit(Why) to exit the current process. If not caught then the
message {‘EXIT’,Pid,Why} is broadcast to all linked processes.
I erlang:error(Why) for internal errors, that nobody is really
expected to handle.
I Thus, to catch all exceptions we need the following pattern:
try Expr
catch
_ : _ -> MightyHandler
end
15 / 27
Robust Systems
I We need at least two computers
(/nodes/processes) to make a robust
system: one computer (/node/process)
to do what we want, and one to monitor
the other and take over when errors
happens.
I link(Pid) makes a symmetric link
between the calling process and Pid.
Often we use the spawn_link function
that spawns a new process and link it.
I monitor(process, Pid) makes an
asymmetric link between the calling
process and Pid.
S W
P1 P2
16 / 27
Linking Processes
I If we want to handle when a linked process crashes then we
need to call process_flag(trap_exit, true).
I Thus, we have the following idioms for creating processes:
I Idiom 1, I don’t care:
Pid = spawn(fun() -> … end)
I Idiom 2, I won’t live without her:
Pid = spawn_link(fun() -> … end)
I Idiom 3, I’ll handle the mess-ups:
process_flag(trap_exit, true),
Pid = spawn_link(fun() -> … end),
loop(…).
loop(State) ->
receive
{‘EXIT’, Pid, Reason} -> HandleMess, loop(State);
…
end
17 / 27
Example: Keep Trucking Looping
Suppose that we really most have a phonebook server running at all
times. How do we monitor the phonebook server and restart it if
(when?) it crashes.
18 / 27
Example: Keep Looping
start() -> keep_looping().
request_reply(Pid, Request) -> Ref = make_ref(),
Pid ! {self(), Ref, Request},
receive {Ref, Response} -> Response end.
keep_looping() ->
spawn(fun () ->
process_flag(trap_exit, true),
Worker = spawn_link(fun() -> loop(#{}) end),
supervisor(Worker)
end).
supervisor(Worker) ->
receive
{‘EXIT’, Pid, Reason} ->
io:format(“~p exited because of ~p~n”, [Pid,Reason]),
Pid1 = spawn_link(fun() -> loop(#{}) end),
supervisor(Pid1);
Msg -> Pid ! Msg, supervisor(Pid)
end.
19 / 27
Distributed Programs in Erlang
I Distributed Erlang for tightly coupled computers in a secure
environment.
I spawn(Node, Fun) to spawn a process running Fun on Node
I {RegAtom, Node} ! Mesg sends Mesg to the process registered
as RegAtom at Node.
I monitor_node(Node, Flag) register the calling process for
notification about Node if Flag is true; if Flag is false then
monitoring is turned off.
I Sockets for untrusted environments:
I To build a middleware layer for Erlang nodes
I For inter-language communication.
See the documentation for gen_tcp and gen_udp
20 / 27
http://www.erlang.org/doc/man/gen_tcp.html
http://www.erlang.org/doc/man/gen_udp.html
Setting Up Some Erlang Nodes
I To start nodes on the same machine, start erl with option
-sname
I To start nodes on different machines, start erl with options
-name and -setcookie:
I On machine A:
erl -name bart -setcookie BoomBoomShakeTheRoom
I On machine B:
erl -name homer -setcookie BoomBoomShakeTheRoom
I rpc:call(Node, Mod, Fun, Args) evaluates Mod:Fun(Args)
on Node. (See the the manual page for rpc for more
information.)
21 / 27
http://www.erlang.org/doc/man/rpc.html
http://www.erlang.org/doc/man/rpc.html
Generic Servers
I Goal: Abstract out the difficult handling of concurrency to a
generic library
I The difficult parts:
I The start–request_reply/async–loop pattern
I Supervisors
22 / 27
Basic Server Library
start(Name, Mod) ->
register(Name, spawn(fun() -> loop(Name, Mod, Mod:init())
end)).
request_reply(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Reply} -> Reply
end.
loop(Name, Mod, State) ->
receive
{From, Request} ->
{Reply, State1} = Mod:handle(Request, State),
From ! {Name, Reply},
loop(Name, Mod, State1)
end.
23 / 27
Example: Phonebook Callback Module, 1
-module(pb).
-import(basicserver, [request_reply/2]).
%% Interface
start() -> basicserver(phonebook, pb).
add(Contact) -> request_reply(pb, {add, Contact}).
list_all() -> request_reply(pb, list_all).
update(Contact) -> request_reply(pb, {update, Contact}).
24 / 27
Example: Phonebook Callback Module, 2
%% Callback functions
init() -> #{}.
handle({add, {Name, _, _} = Contact}, Contacts) ->
case maps:is_key(Name, Contacts) of
false -> {ok, Contacts#{Name => Contact}};
true -> {{error, Name, is_already_there},
Contacts}
end;
handle(list_all, Contacts) ->
List = maps:to_list(Contacts),
{{ok, lists:map(fun({_, C}) -> C end, List)},
Contacts};
handle({update, {Name, _, _} = Contact}, Contacts) ->
{ok, Contacts#{Name => Contact}}.
25 / 27
Flamingo Assignment
Say something about Flamingos.
Remember to say something about ponds.
26 / 27
Summary
I How design micro-server: blocking vs non-blocking
I To make a robust system we need two parts: one to do the job
and one to take over in case of errors
I Structure your code into the infrastructure parts and the
functional parts.
I This week’s assignment: Flamingo
27 / 27