Client-Server Architecture
Common asynchronous communication pattern
- For example: a web server handles requests
for web pages from clients (web browsers)
- For example: a web server handles requests
Mathematical server
Off-loading heavy mathematical operations
- For example: factorial
Server code
loop(Count) -> receive stop -> true ; {get_count, From, Ref} -> From ! {result, Ref, Count}, loop(Count); {factorial, From, Ref, N} -> Result = factorial(N), From ! {result, Ref, Result}, loop(Count+1) end. % starting server with initial state 0 start() -> spawn(fun() -> loop(0) end).
Client code
compute_factorial(Pid, N) -> Ref = make_ref(), Pid ! {factorial, self(), Ref, N}, receive {result, Ref, Result} -> Result end.
A simple test
> c(math_examples). {ok,math_examples} > math_examples:factorial(10). 3628800 > mserver:start(). <0.40.0> > mserver:compute_factorial(list_to_pid("<0.40.0>"),10). 3628800
What if the server crashes or stop?
> list_to_pid("<0.40.0>") ! stop > mserver:compute_factorial(list_to_pid("<0.40.0>"),10). * 2: syntax error before: mserver > mserver:start(). <0.44.0> > mserver:compute_factorial(list_to_pid("<0.44.0>"),10). 3628800
Processes IDs are dynamic but code is static!
- Change in a process ID might lead to notify all the potential clients
Registered processes
Erlang has a method for publishing a process identifier
- Any other process can communicate with it
BIF
register
% starting server with initial state 0 start() -> Pid = spawn(fun() -> loop(0) end), register(server,Pid).
The atom
server
can be used instead of a concrete process ID> mserver2:start(). true > mserver2:compute_factorial(server,10). 3628800 > server ! stop. stop > mserver2:compute_factorial(server,10). ** exception error: bad argument in function mserver2:compute_factorial/2 > mserver2:start(). true > mserver2:compute_factorial(server,10). 3628800
Distributed environments
Message passing abstractions extend easily for distributed environments
Erlang nodes
An instance of an Erlang runtime system
Nodes can easily communicate with each other
Creating a node
erl -name 'nodeS@127.0.0.1' -setcookie lecture
- The cookie provides security (not everyone can connect)
- The name reflects the node's IP address
Creating two nodes (for simplicity on the same machine)
erl -name 'nodeS@127.0.0.1' -setcookie lecture erl -name 'nodeC@127.0.0.1' -setcookie lecture
Connecting nodes
- From
nodeC@127.0.0.1
(nodeC@127.0.0.1)> net_adm:ping('nodeS@127.0.0.1'). pong (nodeC@127.0.0.1)> nodes(). ['nodeS@127.0.0.1']
- From
Distributed mathematical server
Running your code
- Send the compiled version of your code to the connected nodes
(nodeC@127.0.0.1)> nl(math_examples). abcast (nodeC@127.0.0.1)> nl(mserver2). abcast
- The server gets started on the
nodeS
node
(nodeS@127.0.0.1)> mserver2:start(). true
- The client communicates with the server
(nodeC@127.0.0.1)> mserver2:compute_factorial({server, 'nodeS@127.0.0.1'}, 10). 3628800
Use of
{registered_name, node@IP}
instead of the process ID (e.g.<0.47.0>
) or only the registered name (e.g.server
)The code has not been changed for running in a distributed setting!
A generic server
The code must expose the following features:
- Correct
- It implements a proper server/client request/reply interaction
- Parametrized
- It is parametric on the engine (i.e. what the server does)
- Robust
- It does not crash if the engine goes wrong
- Upgradable
- It allows to upgrade the engine of the server without shutting it down
We will achieve all of this with approximately 20 lines of code!
Generic server's code
The engine of the server consists of a function F
of the
following type:
F :: (State,Data) -> (Result, NewState)
loop(State, F) -> receive stop -> true ; {update, From, Ref, NewF} -> From ! {ok, Ref}, loop(State, NewF) ; {request, From, Ref, Data} -> {reply, R, NS} = F(State, Data), From ! {result, Ref, R}, loop(NS, F) end.
It is correct, parametrized, and upgradable
Where can the server go wrong?
Exceptions
The evaluation of expressions can fail
Arithmetic error
> 1/0. ** exception error: bad argument in an arithmetic expression in operator '/'/2 called as 1 / 0
Bad pattern matching
[] = [1]. ** exception error: no match of right hand side value [1]
- Undefined functions
net_adm:ping(1,2). ** exception error: undefined function net_adm:ping/2
- Function
catch
catches exceptions and convert them in a tuple of the form{'EXIT', Reason}
> catch(1/0). {'EXIT',{badarith,[{erlang,'/',[1,0]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,7}, {shell,eval_exprs,7}, {shell,eval_loop,3}]}} > catch([] = [1]). {'EXIT',{{badmatch,[1]},[{erl_eval,expr,3}]}} > catch(net_adm:ping(1,2)). {'EXIT',{undef,[{net_adm,ping,[1,2]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,7}, {shell,eval_exprs,7}, {shell,eval_loop,3}]}} >
Robust generic server
loop(State, F) -> receive stop -> true ; {update, From, Ref, NewF} -> From ! {ok, Ref}, loop(State, NewF); {request, From, Ref, Data} -> case catch(F(State, Data)) of {'EXIT', Reason} -> From!{exit, Ref, Reason}, loop(State, F); {reply, R, NewState} -> From!{result, Ref, R}, loop(NewState, F) end end.
Generic client
- Requests
request(Pid, Data) -> Ref = make_ref(), Pid!{request, self(), Ref, Data}, receive {result, Ref, Result} -> Result; {exit, Ref, Reason} -> exit(Reason) end.
- It propagates the exception from the server to the client
- Upgrade of the server's engine
update(Pid, Fun) -> Ref = make_ref(), Pid!{update, self(), Ref, Fun}, receive {ok, Ref} -> ok end.
Mathematical server revisited
The code for a generic server takes care of the communication, faults, and upgrades
Programmers then only focus on writing the engine
No communication primitives
Mathematical server using the generic server
engine(Count, {factorial,N}) -> Result = math_examples:factorial(N), {reply, Result, Count+1} ; engine(Count, get_count) -> {reply, Count, Count}. start() -> genserver:start(server, 0, fun engine/2). compute_factorial(Pid, N) -> genserver:request(Pid, {factorial, N}).
- Observe that there are no message passing primitives!
> genservermath:start(). <0.130.0> > genservermath:compute_factorial(server,10). 3628800