COP 4020 Lecture -*- Outline -*- * actors ------------------------------------------ ACTORS def: An *actor* is a process that communicates with other processes by sending messages. i.e., an Erlang process Used as clients, servers, nodes in distributed systems Two types: - stateless (state doesn't change) - stateful ------------------------------------------ Q: Why would you want to write a server without any state? That scales better, easy to restart (fault tolerant) ** stateless servers *** math server ------------------------------------------ COMPUTATION SERVER EXAMPLE Math server, sends a message of the form {ok,Val} to Pid, where Val = math:f(math:g(... (math:h(X)) ...)) when it receives messages of the form: {Pid, compute, [f, g, ..., h], X} -module(mathserver). -export([start/0,compute/3]). start() -> mserver() -> accumulate ------------------------------------------ see mathserver.erl ------------------------------------------ CLIENT CODE % (written in the mathserver module) compute(P,Funs,Val) -> P!{self(),compute,Funs,Val}, receive {ok,Res} -> Res end. With this can do: P = mathserver:start(). mathserver:compute(P, [sin,cos], 1.0). ------------------------------------------ *** exercise ------------------------------------------ FOR YOU TO DO Write a stateless server to compute factorials (of integers). ------------------------------------------ Also do that in parallel. ** stateful servers *** print spooler ------------------------------------------ PRINT SPOOLER EXAMPLE Spooler process handles messages of forms: {Client, print, Str} from a Client process, to print string Str {Printer, grab} from Printer process, to get a job to print {Client, status} from a Client process to return status of the spooler. ------------------------------------------ see printspooler.erl ------------------------------------------ PRINT SPOOLER CODE start() -> init() -> loop({queue, Jobs, Printers, Next}) -> ------------------------------------------ Note that the ets tables have to be created in the process using them! So they go in init, not in start! Also in ets:insert, you have to use a tuple, even if it's only a singleton. start() -> spawn(?MODULE, init, []). init() -> % the ets tables have to be created in the new process! JobsQueue = ets:new(queue, [ordered_set]), PrinterQueue = ets:new(waiting, [set]), loop({queue, JobsQueue, PrinterQueue, 1}). loop({queue, Jobs, Printers, Next}) -> receive {Client, print, Str} -> Client!{job_received, Next}, case ets:first(Printers) of '$end_of_table' -> % no printers waiting for work ets:insert(Jobs, {Next, Str}); Printer -> ets:delete(Printers, Printer), Printer!{job, Str, Next} end, loop({queue, Jobs, Printers, Next+1}); {Printer, grab} -> case ets:first(Jobs) of '$end_of_table' -> % no jobs ready to print ets:insert(Printers, {Printer}); K -> Printer!{job, ets:lookup(Jobs, K), K}, ets:delete(Jobs, K) end, loop({queue, Jobs, Printers, Next}); {Client, status} -> Client!{status, {queue, ets:tab2list(Jobs), printers, ets:tab2list(Printers)}}, loop({queue, Jobs, Printers, Next}) end. ------------------------------------------ PRINTER Sends {Me, grab} messages to spooler, then prints them start(Spooler) -> init(Spooler) -> loop({Me, Spooler}) -> grab(Spooler, Me) -> ------------------------------------------ see printer.erl % $Id$ -module(printer). -export([start/1, init/1]). % The state of the printer consists of a tuple with 2 Pids: % {Me, Spooler} % where Me is this server's Pid, and Spooler is the Pid of the spooler. start(Spooler) -> spawn(printer, init, [Spooler]). init(Spooler) -> loop({self(), Spooler}). loop({Me, Spooler}) -> grab(Spooler, Me), loop({Me, Spooler}). grab(Spooler, Me) -> Spooler!{Me, grab}, receive {job, Str, N} -> io:format("JOB ~p~n", [N]), io:format("~s~n", [Str]); Msg -> io:format("printer got bad message: ~p~n", [Msg]) after 900000 -> io:format("printer timed out!~n") end. ------------------------------------------ CLIENTS Asking to print by sending {Pid, print, Str} messages to spooler. Asking for status by sending {Pid, status} messages to spooler print(Spooler, Str) -> status(Spooler) -> ------------------------------------------ see printclients.erl % $Id$ -module(printclients). -export([print/2, status/1]). % function for client use in the printserver example % send a print job to the spooler, and return the job number print(Spooler, Str) -> Spooler!{self(), print, Str}, receive {job_received, N} -> N; Msg -> io:format("print bad message received: ~p~n", [Msg]) end. status(Spooler) -> Spooler!{self(), status}, receive {status, Msg} -> Msg; Msg -> io:format("status bad message received: ~p~n", [Msg]) end. *** resource arbiter ------------------------------------------ RESOURCE ARBITER Server that responds to 3 kinds of messages {Pid, status} sends Pid message: free or inUse {Pid, reserve} eventually sends Pid reserved, when no other process is using resource {Pid, release} lets the resource be used by another process ------------------------------------------ ------------------------------------------ WHAT STATE? What information does the resourcearbiter need? ------------------------------------------ At least a queue of waiting pids (who have sent reserve) To do this we can keep an ets ordered_set, and track a next ------------------------------------------ RESOURCEARBITER MODULE -module(resourcearbiter). -export([start/0, init/0]). -export_type([status/0]). start() -> spawn(?MODULE, init, []). init() -> loop( ------------------------------------------ ... see resourcearbiter*.erl % $Id: resourcearbiter.erl,v 1.1 2013/04/12 00:33:46 leavens Exp leavens $ -module(resourcearbiter). -export([start/0, init/0]). -export_type([status/0]). % The state of the resource arbiter is a tuple of the form % {state, Status, Waiting, Next} % where Status is of type status(), -type status() :: free | inUse. % Waiting is an ets ordered_set of pairs of the type {integer(), pid()}, % and Next is a pos_integer(). start() -> spawn(?MODULE, init, []). init() -> WaitQ = ets:new(queue, [ordered_set]), loop({state, free, WaitQ, 1}). loop({state, Status, Waiting, Next}) -> receive {Client, 'query'} -> Client ! Status, loop({state, Status, Waiting, Next}); {Client, reserve} -> case Status of free -> Client ! reserved, loop({state, inUse, Waiting, Next}); inUse -> ets:insert(Waiting, {Next, Client}), loop({state, Status, Waiting, Next+1}) end; {_Client, release} -> % assert Status == inUse case ets:first(Waiting) of '$end_of_table' -> loop({state, free, Waiting, Next}); {N,Pid} -> Pid ! reserved, ets:delete(Waiting, {N,Pid}), loop({state, inUse, Waiting, Next}) end end.