程序代写代做代考 concurrency Erlang Advanced Programming – Erlang Introduction — The Sequel

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