Vous êtes sur la page 1sur 20

RSS Code Designing a Concurrent Application All fine and dandy.

. You understand the concepts, but then again, all we !e had since the beginning of the boo" were toy e#amples$ calculators, trees, Heathrow to %ondon, etc. &t s time for something more fun and more educational. 'e ll write a small application in concurrent (rlang. )he application s going to be small and line*based, but still useful and moderately e#tensible. & m a somewhat unorgani+ed person. & m lost with homewor", things to do around the apartment, this boo", wor", meetings, appointments, etc. & end up ha!ing a do+en of lists e!erywhere with tas"s & still forget to do or loo" o!er. Hopefully you still need reminders of what to do ,but you don t ha!e a mind that wanders as much as mine does-, because we re going to write one of these e!ent reminder applications that prompt you to do stuff and remind you about appointments. Understanding the Problem )he first step is to "now what the hell we re doing. .A reminder app,. you say. ./f course,. & say. 0ut there s more. How do we plan on interacting with the software1 'hat do we want it to do for us1 How do we represent the program with processes1 How do we "now what messages to send1 As the 2uote goes, .'al"ing on water and de!eloping software from a specification are easy if both are fro+en.. So let s get a spec and stic" to it. /ur little piece of software will allow us to do the following$
Add an e!ent. (!ents contain a deadline ,the time to warn at-, an e!ent name and a description. Show a warning when the time has come for it. Cancel an e!ent by name. 3o persistent dis" storage. &t s not needed to show the architectural concepts we ll see. &t will suc" for a real app, but & ll instead 4ust show where it could be inserted if you wanted to do it and also point to a few helpful functions. 5i!en we ha!e no persistent storage, we ha!e to be able to update the code while it is running. )he interaction with the software will be done !ia the command line, but it should be possible to later e#tend it so other means could be used ,say a 56&, web page access, instant messaging software, email, etc.Home FAQ

Here s the structure of the program & pic"ed to do it$

'here the client, e!ent ser!er and #, y and + are all processes. Here s what each of them can do$ Event Server
Accepts subscriptions from clients Forwards notifications from e!ent processes to each of the subscribers Accepts messages to add e!ents ,and start the #, y, + processes neededCan accept messages to cancel an e!ent and subse2uently "ill the e!ent processes Can be terminated by a client Can ha!e its code reloaded !ia the shell.

client
Subscribes to the e!ent ser!er and recei!e notifications as messages. As such it should be easy to design a bunch of clients all subscribing to the e!ent ser!er. (ach of these could potentially be a gateway to the different interaction points mentioned abo!e ,56&, web page, instant messaging software, email, etc.As"s the ser!er to add an e!ent with all its details As"s the ser!er to cancel an e!ent 7onitors the ser!er ,to "now if it goes downShuts down the e!ent ser!er if needed

x, y and z:
Represent a notification waiting to fire ,they re basically 4ust timers lin"ed to the e!ent ser!erSend a message to the e!ent ser!er when the time is up Recei!e a cancellation message and die

3ote that all clients ,&7, mail, etc. which are not implemented in this boo"- are notified about all e!ents, and a cancellation is not something to warn the clients about. Here the software is written for you and me, and it s assumed only one user will run it. Here s a more comple# graph with all the possible messages$

)his represents e!ery process we ll ha!e. 0y drawing all the arrows there and saying they re messages, we !e written a high le!el protocol, or at least its s"eleton. &t should be noted that using one process per e!ent to be reminded of is li"ely going to be o!er"ill and hard to scale in a real world application. Howe!er, for an application you are going to be the sole user of, this is good enough. A different approach could be using functions such as timer$send8after9:*; to a!oid spawning too many processes. Defining the Protocol 3ow that we "now what each component has to do and to communicate, a good idea would be to ma"e a list of all messages that will be sent and specify what they will loo" li"e. %et s first start with the communication between the client and the e!ent ser!er$

Here & chose to use two monitors because there is no ob!ious dependency between the client and the ser!er. & mean, of course the client doesn t wor" without the ser!er, but the ser!er can li!e without a client. A lin" could ha!e done the 4ob right here, but because we want our system to be e#tensible with many clients, we can t assume other clients will all want to crash when the ser!er dies.

And neither can we assume the client can really be turned into a system process and trap e#its in case the ser!er dies. 3ow to the ne#t message set$

)his adds an e!ent to the e!ent ser!er. A confirmation is sent bac" under the form of the o"atom, unless something goes wrong ,maybe the )ime/ut is in the wrong format.- )he in!erse operation, remo!ing e!ents, can be done as follows$

)he e!ent ser!er can then later send a notification when the e!ent is due$

)hen we only need the two following special cases for when we want to shut the ser!er down or when it crashes$

3o direct confirmation is sent when the ser!er dies because the monitor will already warn us of that. )hat s pretty much all that will happen between the client and the e!ent ser!er. 3ow for the messages between the e!ent ser!er and the e!ent processes themsel!es. A thing to note here before we start is that it would be !ery useful to ha!e the e!ent ser!er lin"ed to the e!ents. )he reason for this is that we want all e!ents to die if the ser!er does$ they ma"e no sense without it. /", so bac" to the e!ents. 'hen the e!ent ser!er starts them, it gi!es each of them a special identifier ,the e!ent s name-. /nce one of these e!ents time has come, it needs to send a message saying so$

/n the other hand, the e!ent has to watch for cancel calls from the e!ent ser!er$

And that should be it. /ne last message will be needed for our protocol, the one that lets us upgrade the ser!er$

3o reply is necessary. 'e ll see why when we actually program that feature and you ll see it ma"es sense. Ha!ing both the protocol defined and the general idea of how our process hierarchy will loo" in place, we can actually start wor"ing on the pro4ect. Lay Them Foundations )o begin with it all, we should lay down a standard (rlang directory structure, which loo"s li"e this$

ebin/

include/

priv/

src/ )he ebin9 directory is where files will go once they are compiled. )he include9 directory is used to store .hrl files that are to be included by other applications< the pri!ate .hrl files are usually 4ust "ept inside the src9 directory. )he pri!9 directory is used for e#ecutables that might ha!e to interact with (rlang, such as specific dri!ers and whatnot. 'e won t actually use that directory for this pro4ect. )hen the last one is the src9directory, where all .erl files stay. &n standard (rlang pro4ects, this directory structure can !ary a little. A conf9 directory can be added for specific configuration files, doc9 for documentation and lib9 for third party libraries re2uired for your application to run. =ifferent (rlang product on the mar"et often use different names than these, but the four ones mentioned abo!e usually stay the same gi!en they re part of the standard /)> practices. An Event Module 5et into the src9 directory and start an e!ent.erl module, which will implement the

#, y and + e!ents in the earlier drawings. & m starting with this module because it s the one with the fewest dependencies$ we ll be able to try to run it without needing to implement the e!ent ser!er or client functions. 0efore really writing code, & ha!e to mention that the protocol is incomplete. &t helps represent what data will be sent from process to process, but not the intricacies of it$ how the addressing wor"s, whether we use references or names, etc. 7ost messages will be wrapped under the form?>id, Ref, 7essage@, where Pid is the sender and Ref is a uni2ue message identifier to help "now what reply came from who. &f we were to send many messages before loo"ing for replies, we would not "now what reply went with what message without a reference. So here we go. )he core of the processes that will run e!ent.erl s code will be the functionloop9A, which will loo" a bit li"e the following s"eleton if you remember the protocol$
loop(State) -> receive {Server, Ref, cancel} -> ... after Delay -> ... end.

This shows the timeout we have to support to announce an event has come to term and the way a server can call for the cancellation of an event. You'll notice a variable State in the loop. TheState variable will have to contain data such as the timeout value in seconds! and the name of the event in order to send the message "done# $d%.! $t will also need to &now the event server's pid in order to send it notifications. )his is all stuff that s fit to be held in the loop s state. So let s declare a state record on the top of the file$
-module(event). -compile(export_all). -record(state, {server, name="", to_go= }).

'ith this state defined# it should be possible to refine the loop a bit more(
loop(S = !state{server=Server}) -> receive {Server, Ref, cancel} -> Server ! {Ref, o"} after S!state#to_go*$ -> Server ! {done, S!state#name} end.

)ere# the multiplication by a thousand is to change the to*go value from seconds to milliseconds.
=on t drin" too much Bool*Aid$

%anguage wart aheadC )he reason why & bind the !ariable Ser!er in the function head is because it s used in pattern matching in the recei!e section. Remember,records are hac"sC )he e#pression SDstate.ser!er is secretly e#panded toelement,:, S-, which isn t a !alid pattern to match on. )his still wor"s fine for SDstate.to8go after the after part, because that one can be an e#pression left to be e!aluated later.

3ow to test the loop$


6> c(event). {ok,event} 7> rr(event, state). [state] 8> spa%n(event, loop, &!state{server'self(), name="test", to_go=(})). <0.60.0> 9> flus*(). ok 10> flus*(). Shell got {done,"test"} o" 11> +id = spa%n(event, loop, &!state{server'self(), name="test", to_go=( <0.64.0> 12> ReplyRef = ma"e_ref(). !e"<0.0.0.210> 1#> +id ! {self(), ReplyRef, cancel}. {<0.$0.0>, !e"<0.0.0.210>,%an%el} 14> flus*(). Shell got { !e"<0.0.0.210>,ok} ok

})).

+ots of stuff to see here. 'ell first of all# we import the record from the event module withrr ,od!. Then# we spawn the event loop with the shell as the server self !!. This event should fire after seconds. The .th e/pression was run after 0 seconds# and the 12th one after 3 seconds. You can see we did receive the "done# 4test4% message on the second try. Right after that, & try the cancel feature ,with an ample EFF seconds to type it-. You can see & created the reference, sent the message and got a reply with the same reference so & "now the o"& recei!ed was coming from this process and not any other on the system. )he reason why the cancel message is wrapped with a reference but the done message isn t is simply because we don t e#pect it to come from anywhere specific ,any place will do, we won t match on the recei!e- nor should we want to reply to it. )here s another test & want to do beforehand. 'hat about an e!ent happening ne#t year1
1$> spa%n(event, loop, &!state{server'self(), name="test", to_go=,-(*./*- *- })). <0.69.0> 16> &'!!(! !')(!*&&&& ++,--,....//00/11/SS &&& '22o2 3n 42o%ess <0.69.0> 53th e63t val7e/ {t31eo7t8val7e,[{event,loo4,1}]}

5uch. $t seems li&e we hit an implementation limit. $t turns out 6rlang's timeout value is limited to about -2 days in milliseconds. $t might not be significant# but $'m showing this error for three reasons(
A. &t bit me in the ass when writing the module and testing it, halfway through the chapter. :. (rlang is certainly not perfect for e!ery tas" and what we re seeing here is the conse2uences of using timers in ways not intended by the implementers. ;. )hat s not really a problem< let s wor" around it.

)he fi# & decided to apply for this one was to write a function that would split the timeout !alue into many parts if turns out to be too long. )his will re2uest some support from the loop9Afunction too. So yeah, the way to split the time is basically di!ide it in e2ual parts of GH days ,because the limit is about EF-, and then put the remainder with all these e2ual parts. )he sum of the list of seconds should now be the original time$

99 :e%a7se '2lang 3s l313ted to a;o7t 49 da<s =49>24>60>60>1000? 3n 99 13ll3se%onds, the "ollo53ng "7n%t3on 3s 7sed normali0e(1) -> 2imit = /3*./*- *- , &1 rem 2imit | lists4duplicate(1 div 2imit, 2imit)).

The function lists(duplicate/7 will ta&e a given e/pression as a second argument and reproduce it as many times as the value of the first argument 8a#a#a9 : lists(duplicate 0# a!!. $f we were to send normali;e/1 the value .<=7>=32=32?># it would return 8>#>700322#>7003229. The loop/1function should now loo& li&e this to accommodate the new format(
99 @oo4 7ses a l3st "o2 t31es 3n o2de2 to go a2o7nd the A49 da<s l313t 99 on t31eo7ts. loop(S = !state{server=Server, to_go=&5|1ext)}) -> receive {Server, Ref, cancel} -> Server ! {Ref, o"} after 5*$ -> if 1ext =:= &) -> Server ! {done, S!state#name}; 1ext =/= &) -> loop(S!state{to_go=1ext}) end end.

You can try it# it should wor& as normal# but now support years and years of timeout. )ow this wor&s is that it ta&es the first element of the to*go list and waits for its whole duration. 'hen this is done# the ne/t element of the timeout list is verified. $f it's empty# the timeout is over and the server is notified of it. 5therwise# the loop &eeps going with the rest of the list until it's done. &t would be !ery annoying to ha!e to manually call something li"e e!ent$normali+e,3- e!ery time an e!ent process is started, especially since our wor"around shouldn t be of concern to programmers using our code. )he standard way to do this is to instead ha!e an init function handling all initiali+ation of data re2uired for the loop function to wor" well. 'hile we re at it, we ll add the standard start and start8lin" functions$
start(6vent1ame, Delay) -> spa%n(789D:26, init, &self(), 6vent1ame, Delay)). start_lin"(6vent1ame, Delay) -> spa%n_lin"(789D:26, init, &self(), 6vent1ame, Delay)). 999 'ventBs 3nna2ds init(Server, 6vent1ame, Delay) -> loop(!state{server=Server, name=6vent1ame, to_go'normali0e(Delay)}).

The interface is now much cleaner. @efore testing# though# it would be nice to have the only message we can send# cancel# also have its own interface function(
cancel(+id) -> 99 -on3to2 3n %ase the 42o%ess 3s al2ead< dead Ref = erlang4monitor(process, +id), +id ! {self(), Ref, cancel}, receive {Ref, o"} -> erlang4demonitor(Ref, &flus*)),

o"; {;D9<1;, Ref, process, +id, _Reason} -> o" end.

5hA A new tric&A )ere $'m using a monitor to see if the process is there or not. $f the process is already dead# $ avoid useless waiting time and return o& as specified in the protocol. $f the process replies with the reference# then $ &now it will soon die( $ remove the reference to avoid receiving them when $ no longer care about them. Bote that $ also supply the flush option# which will purge the D5'B message if it was sent before we had the time to demonitor. %et s test these$
17> c(event). {ok,event} 18> f(). ok 19> event4start("6vent", ). <0.10#.0> 20> flus*(). Shell got {done,"'vent"} o" 21> +id = event4start("6vent", ( <0.106.0> 22> event4cancel(+id). o"

).

And it wor&sA The last thing annoying with the event module is that we have to input the time left in seconds. $t would be much better if we could use a standard format such as 6rlang's datetime ""Year# ,onth# Day%# ")our# ,inute# Second%%!. Cust add the following function that will calculate the difference between the current time on your computer and the delay you inserted(
time_to_go(5ime9ut={{_,_,_}, {_,_,_}}) -> 1o% = calendar4local_time(), 5o=o = calendar4datetime_to_gregorian_seconds(5ime9ut) calendar4datetime_to_gregorian_seconds(1o%), Secs = if 5o=o > -> 5o=o; 5o=o =< -> end, normali0e(Secs).

5h# yeah. The calendar module has pretty fun&y function names. As noted above# this calculates the number of seconds between now and when the event is supposed to fire. $f the event is in the past# we instead return 2 so it will notify the server as soon as it can. Bow fi/ the init function to call this one instead of normali;e/1. You can also rename Delay variables to say DateTime if you want the names to be more descriptive(
init(Server, 6vent1ame, Date5ime) -> loop(!state{server=Server, name=6vent1ame, to_go'time_to_go(Date5ime)}).

Bow that this is done# we can ta&e a brea&. Start a new event# go drin& a pint halfDlitre! of mil&/beer and come bac& Eust in time to see the event message coming in. The Event Server %et s deal with the e!ent ser!er. According to the protocol, the s"eleton for that one should loo" a bit li"e this$
-module(evserv). -compile(export_all).

loop(State) -> receive {+id, 8sgRef, {su>scri>e, ?lient}} -> ... {+id, 8sgRef, {add, 1ame, Description, 5ime9ut}} -> ... {+id, 8sgRef, {cancel, 1ame}} -> ... {done, 1ame} -> ... s*utdo%n -> ... {;D9<1;, Ref, process, _+id, _Reason} -> ... code_c*ange -> ... :n"no%n -> io4format(":n"no%n message4 @p@n",&:n"no%n)), loop(State) end.

You'll notice $ have wrapped calls that reFuire replies with the same "Gid# Hef# ,essage% format as earlier. Bow# the server will need to &eep two things in its state( a list of subscribing clients and a list of all the event processes it spawned. $f you have noticed# the protocol says that when an event is done# the event server should receive "done# Bame%# but send "done# Bame# Description%. The idea here is to have as little traffic as necessary and only have the event processes care about what is strictly necessary. So yeah# list of clients and list of events(
-record(state, {events, 99 l3st o" event{} 2e%o2ds clients}). 99 l3st o" )3ds -record(event, {name="", description="", pid, timeout={{$3A ,$,$},{ , , }}}).

And the loop now has the record definition in its head(
loop(S = !state{}) -> receive ... end.

$t would be nice if both events and clients were orddicts. 'e're unli&ely to have many hundreds of them at once. $f you recall the chapter on data structures# orddicts fit that need very well. 'e'll write an init function to handle this(
init() -> 99 @oad3ng events "2o1 a stat3% "3le %o7ld ;e done he2e. 99 .o7 5o7ld need to 4ass an a2g71ent to 3n3t tell3ng 5he2e the 99 2eso72%e to "3nd the events 3s. *hen load 3t "2o1 he2e. 99 Cnothe2 o4t3on 3s to D7st 4ass the events st2a3ght to the se2ve2 99 th2o7gh th3s "7n%t3on. loop(!state{events'orddict4ne%(), clients'orddict4ne%()}).

'ith the s&eleton and initiali;ation done# $'ll implement each message one by one. The first message is the one about subscriptions. 'e want to &eep a list of all subscribers because when an event is done# we have to notify them. Also# the protocol above mentions we should monitor them. $t ma&es sense because we don't want to hold onto crashed clients and send useless messages for no reason. Anyway# it

should loo& li&e this(


{+id, 8sgRef, {su>scri>e, ?lient}} -> Ref = erlang4monitor(process, ?lient), 1e%?lients = orddict4store(Ref, ?lient, S!state#clients), +id ! {8sgRef, o"}, loop(S!state{clients=1e%?lients});

So what this section of loop/1 does is start a monitor# and store the client info in the orddict under the &ey Hef. The reason for this is simple( the only other time we'll need to fetch the client $D will be if we receive a monitor's 6I$T message# which will contain the reference which will let us get rid of the orddict's entry!. )he ne#t message to care about is the one where we add e!ents. 3ow, it is possible to return an error status. )he only !alidation we ll do is chec" the timestamps we accept. 'hile it s easy to subscribe to the??Year,7onth,=ay@, ?Hour,7inute,seconds@@ layout, we ha!e to ma"e sure we don t do things li"e accept e!ents on February :H when we re not in a leap year, or any other date that doesn t e#ist. 7oreo!er, we don t want to accept impossible date !alues such as .E hours, minus A minute and IE seconds.. A single function can ta"e care of !alidating all of that. )he first building bloc" we ll use is the function calendar$!alid8date9A. )his one, as the name says, chec"s if the date is !alid or not. Sadly, the weirdness of the calendar module doesn t stop at fun"y names$ there is actually no function to confirm that ?H,7,S@ has !alid !alues. 'e ll ha!e to implement that one too, following the fun"y naming scheme$
valid_datetime({Date,5ime}) -> try calendar4valid_date(Date) andalso valid_time(5ime) catch error4function_clause -> 99 not 3n {{.,-,+},{0,-3n,S}} "o21at false end; valid_datetime(_) -> false. valid_time({B,8,S}) -> valid_time(B,8,S). valid_time(B,8,S) when B >= , B < ./, 8 >= , 8 < - , S >= , S < - -> true; valid_time(_,_,_) -> false.

The valid*datetime/1 function can now be used in the part where we try to add the message(
{+id, 8sgRef, {add, 1ame, Description, 5ime9ut}} -> case valid_datetime(5ime9ut) of true -> 6vent+id = event4start_lin"(1ame, 5ime9ut), 1e%6vents = orddict4store(1ame, !event{name=1ame, description=Description, pid=6vent+id, timeout=5ime9ut}, S!state#events),

+id ! {8sgRef, o"}, loop(S!state{events=1e%6vents}); false -> +id ! {8sgRef, {error, >ad_timeout}}, loop(S) end;

$f the time is valid# we spawn a new event process# then store its data in the event server's state before sending a confirmation to the caller. $f the timeout is wrong# we notify the client rather than having the error pass silently or crashing the server. Additional chec&s could be added for name clashes or other restrictions Eust remember to update the protocol documentationA! )he ne#t message defined in our protocol is the one where we cancel an e!ent. Canceling an e!ent ne!er fails on the client side, so the code is simpler there. Just chec" whether the e!ent is in the process state record. &f it is, use the e!ent$cancel9A function we defined to "ill it and send o". &f it s not found, 4ust tell the user e!erything went right anyway ** the e!ent is not running and that s what the user wanted.
{+id, 8sgRef, {cancel, 1ame}} -> 6vents = case orddict4find(1ame, S!state#events) of {o", 6} -> event4cancel(6!event#pid), orddict4erase(1ame, S!state#events); error -> S!state#events end, +id ! {8sgRef, o"}, loop(S!state{events=6vents});

Jood# good. So now all voluntary interaction coming from the client to the event server is covered. +et's deal with the stuff that's going between the server and the events themselves. There are two messages to handle( canceling the events which is done!# and the events timing out. That message is simply "done# Bame%(
{done, 1ame} -> case orddict4find(1ame, S!state#events) of {o", 6} -> send_to_clients({done, 6!event#name, 6!event#description}, S!state#clients), 1e%6vents = orddict4erase(1ame, S!state#events), loop(S!state{events=1e%6vents}); error -> 99 *h3s 1a< ha44en 3" 5e %an%el an event and 99 3t "32es at the sa1e t31e loop(S) end;

And the function send*to*clients/7 does as its name says and is defined as follows(
send_to_clients(8sg, ?lientDict) -> orddict4map(fun(_Ref, +id) -> +id ! 8sg end, ?lientDict).

That should be it for most of the loop code. 'hat's left is the set different status messages( clients going down# shutdown# code upgrades# etc. )ere they come(
s*utdo%n -> exit(s*utdo%n); {;D9<1;, Ref, process, _+id, _Reason} -> loop(S!state{clients'orddict4erase(Ref, S!state#clients)}) ; code_c*ange ->

789D:264loop(S); :n"no%n -> io4format(":n"no%n message4 @p@n",&:n"no%n)), loop(S)

The first case shutdown! is pretty e/plicit. You get the &ill message# let the process die. $f you wanted to save state to dis&# that could be a possible place to do it. $f you wanted safer save/e/it semantics# this could be done on every add# cancel or done message. +oading events from dis& could then be done in the init function# spawning them as they come. )he =/'3 message s actions are also simple enough. &t means a client died, so we remo!e it from the client list in the state. 6n"nown messages will 4ust be shown with io$format9: for debugging purposes, although a real production application would li"ely use a dedicated logging module And here comes the code change message. )his one is interesting enough for me to gi!e it its own section. Hot Code Loving &n order to do hot code loading, (rlang has a thing called the code ser!er. )he code ser!er is basically a K7 process in charge of an ()S table ,in*memory database table, nati!e to the K7.- )he code ser!er can hold two !ersions of a single module in memory, and both !ersions can run at once. A new !ersion of a module is automatically loaded when compiling it with c,7odule-, loading with l,7odule- or loading it with one of the many functions of the code module. A concept to understand is that (rlang has both local and e#ternal calls. %ocal calls are those function calls you can ma"e with functions that might not be e#ported. )hey re 4ust of the formatAtom,Args-. An e#ternal call, on the other hand, can only be done with e#ported functions and has the form 7odule$Function,Args-. 'hen there are two !ersions of a module loaded in the K7, all local calls are done through the currently running !ersion in a process. Howe!er, e#ternal calls are always done on the newest !ersion of the code a!ailable in the code ser!er. )hen, if local calls are made from within the e#ternal one, they are in the new !ersion of the code.

5i!en that e!ery process9actor in (rlang needs to do a recursi!e call in order to change its state, it is possible to load entirely new !ersions of an actor by ha!ing an e#ternal recursi!e call. 3ote$ &f you load a third !ersion of a module while a process still runs with the first one, that process gets "illed by the K7, which assumes it was an orphan process without a super!isor or a way to upgrade itself. &f nobody runs the oldest !ersion, it is simply dropped and the newest ones are "ept instead. )here are ways to bind yourself to a system module that will send messages whene!er a new !ersion of a module is loaded. 0y doing this, you can trigger a module reload only when recei!ing such a message, and always do it with a code upgrade function, say7y7odule$6pgrade,CurrentState-, which will then be able to transform the state data structure according to the new !ersion s specification. )his subscription handling is done automatically by the /)> framewor", which we ll start studying soon enough. For the reminder application, we won t use the code ser!er and will instead use a custom code8change message from the shell, doing !ery basic reloading. )hat s pretty much all you need to "now to do hot code loading. 3e!ertheless, here s a more generic e#ample$
-module(*otload). -export(&server/$, upgrade/$)). server(State) -> receive update -> 1e%State = 789D:264upgrade(State), 789D:264server(1e%State); 99 loo4 3n the ne5 ve2s3on o" the 1od7le Some8essage -> 99 do so1eth3ng he2e server(State) 99 sta< 3n the sa1e ve2s3on no 1atte2 5hat. end. upgrade(9ldState) ->

99 t2ans"o21 and 2et72n the state he2e.

As you can see# our K,5DL+6(loop S! fits this pattern. I Said, Hide Your Messages Hiding messagesC &f you e#pect people to build on your code and processes, you must hide the messages in interface functions. Here s what we used for the e!ser! module$
start() -> register(789D:26, +id'spa%n(789D:26, init, &))), +id. start_lin"() -> register(789D:26, +id'spa%n_lin"(789D:26, init, &))), +id. terminate() -> 789D:26 ! s*utdo%n.

$ decided to register the server module because# for now# we should only have one running at a time. $f you were to e/pand the reminder use to support many users# it would be a good idea to instead register the names with the global module# or the gproc library. Mor the sa&e of this e/ample app# this will be enough. )he first message we wrote is the ne#t we should abstract away$ how to subscribe. )he little protocol or specification & wrote abo!e called for a monitor, so this one is added there. At any point, if the reference returned by the subscribe message is in a =/'3 message, the client will "now the ser!er has gone down.
su>scri>e(+id) -> Ref = erlang4monitor(process, %*ereis(789D:26)), 789D:26 ! {self(), Ref, {su>scri>e, +id}}, receive {Ref, o"} -> {o", Ref}; {;D9<1;, Ref, process, _+id, Reason} -> {error, Reason} after ( -> {error, timeout} end.

The ne/t one is the event adding(


add_event(1ame, Description, 5ime9ut) -> Ref = ma"e_ref(), 789D:26 ! {self(), Ref, {add, 1ame, Description, 5ime9ut}}, receive {Ref, 8sg} -> 8sg after ( -> {error, timeout} end.

Bote that $ choose to forward the "error# bad*timeout% message that could be received to the client. $ could have also decided to crash the client by raising erlang(error bad*timeout!. 'hether crashing the client or forwarding the error message is the thing to do is still debated in the community. )ere's the alternative crashing function(
add_event.(1ame, Description, 5ime9ut) -> Ref = ma"e_ref(), 789D:26 ! {self(), Ref, {add, 1ame, Description, 5ime9ut}}, receive

{Ref, {error, Reason}} -> erlang4error(Reason); {Ref, 8sg} -> 8sg after ( -> {error, timeout} end.

Then there's event cancellation# which Eust ta&es a name(


cancel(1ame) -> Ref = ma"e_ref(), 789D:26 ! {self(), Ref, {cancel, 1ame}}, receive {Ref, o"} -> o" after ( -> {error, timeout} end.

+ast of all is a small nicety provided for the client# a function used to accumulate all messages during a given period of time. $f messages are found# they're all ta&en and the function returns as soon as possible(
listen(Delay) -> receive 8 = {done, _1ame, _Description} -> &8 | listen( )) after Delay*$ -> &) end.

A Test Drive You should now be able to compile the application and gi!e it a test run. )o ma"e things a bit simpler, we ll write a specific (rlang ma"efile to build the pro4ect. /pen a file named (ma"efileand put it in the pro4ect s base directory. )he file contains (rlang terms and gi!es the (rlang compiler the recipe to coo" wonderful and crispy .beam files$

"'src/='# 8debug*info#

"i# 4src4%#

"i# 4include4%#

"outdir# 4ebin4%9%. )his tells the compiler to add debug8info to the files ,this is rarely an option you want to gi!e up-, to loo" for files in the src9 and include9directory and to output them in ebin9. 3ow, by going in your command line and running erl *ma"e, the files should all be compiled and put inside the ebin9 directory for you. Start the (rlang shell by doing erl *pa ebin9. )he *pa LdirectoryM option tells the (rlang K7 to add that

path to the places it can loo" in for modules. Another option is to start the shell as usual and call ma"e$all,NloadO-. )his will loo" for a file named (ma"efile in the current directory, recompile it ,if it changed- and load the new files. You should now be able to trac" thousands of e!ents ,4ust replace the DateTime !ariables with whate!er ma"es sense when you re writing the te#t-$
1> evserv4start(). <0.#4.0> 2> evserv4su>scri>e(self()). {ok, !e"<0.0.0.#1>} #> evserv4add_event("Bey t*ere", "test", CutureDate5ime). ok 4> evserv4listen((). [] $> evserv4cancel("Bey t*ere"). o" 6> evserv4add_event("Bey t*ere.", "test", 1ext8inuteDate5ime). o" 7> evserv4listen(. ). [{done,"0e< the2e2","test"}]

Bice nice nice. 'riting any client should now be simple enough given the few basic interface functions we have created. Adding Supervision &n order to be a more stable application, we should write another restarter as we did in the last chapter. /pen up a file named sup.erl where our super!isor will be$
-module(sup). -export(&start/., start_lin"/., init/$, loop/$)). start(8od,Drgs) -> spa%n(789D:26, init, &{8od, Drgs})). start_lin"(8od,Drgs) -> spa%n_lin"(789D:26, init, &{8od, Drgs})). init({8od,Drgs}) -> process_flag(trap_exit, true), loop({8od,start_lin",Drgs}). loop({8,C,D}) -> +id = apply(8,C,D), receive {;6EF5;, _Crom, s*utdo%n} -> exit(s*utdo%n); 9 53ll k3ll the %h3ld too {;6EF5;, +id, Reason} -> io4format("+rocess @p exited for reason @p@n",&+id,Reason)), loop({8,C,D}) end.

This is somewhat similar to the 'restarter'# although this one is a tad more generic. $t can ta&e any module# as long as it has a start*lin& function. $t will restart the process it watches indefinitely# unless the supervisor itself is terminated with a shutdown e/it signal. )ere it is in use(
1> c(evserv), c(sup). {ok,s74}

2> Sup+id = sup4start(evserv, &)). <0.4#.0> #> %*ereis(evserv). <0.44.0> 4> exit(%*ereis(evserv), die). t27e )2o%ess <0.44.0> e63ted "o2 2eason d3e $> exit(%*ereis(evserv), die). )2o%ess <0.48.0> e63ted "o2 2eason d3e true 6> exit(Sup+id, s*utdo%n). true 7> %*ereis(evserv). undefined

As you can see# &illing the supervisor will also &ill its child. 3ote$ 'e ll see much more ad!anced and fle#ible super!isors in the chapter about /)> super!isors. )hose are the ones people are thin"ing of when they mention super!ision trees. )he super!isor demonstrated here is only the most basic form that e#ists and is not e#actly fit for production en!ironments compared to the real thing. Namespaces (or lack thereof) 0ecause (rlang has a flat module structure ,there is no hierarchy-, &t is fre2uent for some applications to enter in conflict. /ne e#ample of this is the fre2uently used user module that almost e!ery pro4ect attempts to define at least once. )his clashes with the user module shipped with (rlang. You can test for any clashes with the function code$clash9F. 0ecause of this, the common pattern is to prefi# e!ery module name with the name of your pro4ect. &n this case, our reminder application s modules should be renamed toreminder8e!ser!, reminder8sup andre minder8e!ent. Some programmers then decide to add a module, named after the application itself, which wraps common calls that programmers could use when using their own application. (#ample calls could be functions such as starting the application with a super!isor, subscribing to the ser!er, adding and cancelling e!ents, etc. &t s important to be aware of other namespaces, too, such as registered names that must not clash, database tables, etc. )hat s pretty much it for a !ery basic concurrent (rlang application. )his one showed we could ha!e a bunch of concurrent processes without thin"ing too hard about it$ super!isors, clients, ser!ers, processes used as timers ,and we could ha!e thousands of them-, etc. 3o need to synchroni+e them, no loc"s, no real main loop. 7essage passing has made it simple to compartmentali+e our

application into a few modules with separated concerns and tas"s. )he basic calls inside e!ser!.erl could now be used to construct clients that would allow to interact with the e!ent ser!er from somewhere outside of the (rlang K7 and ma"e the program truly useful. 0efore doing that, though, & suggest you read up on the /)> framewor". )he ne#t few chapters will co!er some of its building bloc"s, which will allow for much more robust and elegant applications. A huge part of (rlang s power comes from using it. &t s a carefully crafted and well*engineered tool that any self*respecting (rlang programmer has to "now.
< Previous Index Next >

6/cept where otherwise noted# content on this site is licensed under a Creative Commons Attribution BonDCommercial Bo Derivative +icense

Vous aimerez peut-être aussi