The code must expose the following features:
We will achieve all of this with approximately 20 lines of 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} -> {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?
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]
net_adm:ping(1,2). ** exception error: undefined function net_adm:ping/2
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}]}} >
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); {R, NewState} -> From!{result, Ref, R}, loop(NewState, F) end end.
request(Pid, Data) -> Ref = make_ref(), Pid!{request, self(), Ref, Data}, receive {result, Ref, Result} -> Result; {exit, Ref, Reason} -> exit(Reason) end.
update(Pid, Fun) -> Ref = make_ref(), Pid!{update, self(), Ref, Fun}, receive {ok, Ref} -> ok end.
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), {Result, Count+1} ; engine(Count, get_count) -> {Count, Count}. start() -> genserver:start(server, 0, fun engine/2). compute_factorial(Pid, N) -> genserver:request(Pid, {factorial, N}).
> genservermath:start(). <0.130.0> > genservermath:compute_factorial(server,10). 3628800
Message passing
Barrier synchronisation
Resource allocation
Readers and writers
N processes must wait for the slowest before continuing with the next activity
Widely used in parallel programming
reach_wait(Server) -> Ref = make_ref(), Server ! {reach, self(), Ref}, receive {ack, Ref} -> true end. start(N) -> Pid = spawn(fun() -> coordinator(N,N,[]) end), register(coordinator, Pid). coordinator(N,0,Ps) -> [ From ! {ack, Ref} || {From, Ref} <- Ps ], coordinator(N,N,[]) ; coordinator(N,M,Ps) -> receive {reach, From, Ref} -> coordinator(N,M-1, [ {From,Ref} | Ps]) end.
A controller controls access to copies of some resources (of the same kind)
Clients requiring multiple resources should not ask for resources one at a time
Clients make requests to take or return any number of the resources
A request should only succeed if there are
sufficiently many resources available (see line 4
)
Otherwise the request must block
Function lists:sublist
returns a slice of a
list
(more here)
loop(Resources) -> Available = length(Resources), receive {req, From, Ref, Number} when Number =< Available -> From ! {res, Ref, lists:sublist(Resources, Number)}, loop(lists:sublist(Resources, Number+1, Available)) ; {ret, List} -> loop(lists:append(Resources, List)) end. start(Init) -> Pid = spawn (fun () -> loop(Init) end), register(rserver, Pid). request(N) -> Ref = make_ref(), rserver ! {req, self(), Ref, N}, receive {res, Ref, List} -> List end. release(List) -> rserver ! {ret, List}, ok
> c(ralloc). {ok,ralloc} > ralloc:start([1,1,1,1]). true > ralloc:request(3). [1,1,1] > ralloc:release([1]). ok > ralloc:request(2). [1,1] > ralloc:request(10).
Two kinds of processes share access to a “database”
Readers examine the contents
Writers examine and modify data
Readers and writers in a few lines
loop(Rs, Ws) -> receive {start_read, From, Ref} when Ws =:= 0 -> From ! {ok_to_read, Ref}, loop(Rs+1,Ws) ; {start_write, From, Ref} when Ws =:= 0 and Rs =:= 0 -> From ! {ok_to_write, Ref}, loop(Rs, Ws+1) ; end_read -> loop(Rs-1, Ws) ; end_write -> loop(Rs, Ws-1) end.
<received event>, <condition> / <triggered event>
loop() -> receive {start_read, From, Ref} -> From ! {ok_to_read, Ref}, loop_read(1), loop() ; {start_write, From, Ref} -> From ! {ok_to_write, Ref}, receive end_write -> loop() end end. loop_read(0) -> ok ; loop_read(Rs) -> receive {start_read, From, Ref} -> From ! {ok_to_read, Ref}, loop_read(Rs+1) ; end_read -> loop_read(Rs-1) ; {start_write, From, Ref} -> [ receive end_read -> ok end || _ <- lists:seq(1,Rs) ], From ! {ok_to_write, Ref}, receive end_write -> ok end end.
At top-level function loop
relies on the fairness property
of Erlang (i.e. the oldest message that matches any guard
is processed)
Function loop_read
implements fairness
Line [ receive end_read -> ok end || _ <- lists:seq(1,Rs) ]
performs as many receive
as the number Rs