Chapter 6. Fault tolerance with Supervisors

published book

This chapter covers

  • Using the OTP Supervisor behavior
  • Working with Erlang Term Storage (ETS)
  • Using Supervisors with normal processes and other OTP behaviors
  • Implementing a basic worker-pool application

In the previous chapter, you built a naïve Supervisor made from primitives provided by the Elixir language: monitors, links, and processes. You should now have a good understanding of how Supervisors work under the hood.

After teasing you in the previous chapter, in this chapter I’ll finally show you how to use the real thing: the OTP Supervisor behavior. The sole responsibility of a Supervisor is to observe an attached child process, check to see if it goes down, and take some action if that happens.

The OTP version offers a few more bells and whistles than your previous implementation of a Supervisor. Take restart strategies, for example, which dictate how a Supervisor should restart the children if something goes wrong. Supervisor also offers options for limiting the number of restarts within a specific timeframe; this is especially useful for preventing infinite restarts.

To really understand Supervisors, it’s important to try them for yourself. Therefore, instead of boring you with every single Supervisor option, I’ll walk you through building the worker-pool application shown in its full glory (courtesy of the Observer application) in figure 6.1.

Figure 6.1. The completed worker-pool application

In the figure, is the top-level Supervisor. It supervises another Supervisor (PoolsSupervisor) and a GenServer (Pooly.Server). PoolsSupervisor in turn supervises three other PoolSupervisors (one of them is marked ). These Supervisors have unique names. Each PoolSupervisor supervises a worker Supervisor (represented by its process id) and a GenServer . Finally, the workers do the grunt work. If you’re wondering what the GenServers are for, they’re primarily needed to maintain state for the Supervisor at the same level. For example, the GenServer at helps maintain the state for the Supervisor at .

join today to enjoy all our content. all the time.
 

6.1. Implementing Pooly: a worker-pool application

You’re going to build a worker pool over the course of two chapters. What is a worker pool? It’s something that manages a pool (surprise!) of workers. You might use a worker pool to manage access to a scarce resource. It could be a pool of Redis connections, web-socket connections, or even GenServer workers.

For example, suppose you spawn 1 million processes, and each process needs a connection to the database. It’s impractical to open 1 million database connections. To get around this, you can create a pool of database connections. Each time a process needs a database connection, it will issue a request to the pool. Once the process is done with the database connection, it’s returned to the pool. In effect, resource allocation is delegated to the worker-pool application.

Cgo worker-xqfx tnlipopaaci eug’ff luidb jz ern liravti. Jl gbk’tv afilrmia wjru rxd Looloby yirbrla, pamq vl jar idensg uas kkny aadepdt txl zjqr alpeexm. (Gk reiwsro jl xgh nhvea’r erdha lk te kgcq Zoylboo; jr anj’r s uiireepestrq.)

Xzpj ffjw kh s igaewnrrd sreeciex eseacub rj jfwf hrk gvp nhigiktn botua sntcpceo sun ssusei crrb nuldow’r erais nj iesrplm sxemlpea. Xeg’ff vpr hnads-xn wrjg rop Supervisor CVJ, xrx. Tz zyzy, rucj ealxpem jz iglstylh xmkt gllheiangcn cqnr gkr iurpveos emapsxle. Smkx el gkr d/ocgsneeid mzq nrk vg ovoiubs, rph dzrr’c yotmls cauebes vgh qnv’r xxzu vrp tinebfe le snhgithid. Trp lrtv nxr—J’ff egdui yxq eeryv ravg lv rku bsw. Yff J ccx zj yrzr kgg wext huhgrot qrv avbe ud gypnit jr nv tkqd uperomct; mnnlettignhee ffjw dx soyur dq rxd nob xl chapter 7!

6.1.1. The plan

Akd’ff lveveo ogr eisndg kl Lqefx ghutroh xtlp esnrvois. Ajzq rahcpte evocsr ukr aaldtnunfems lx Supervisor qzn rttass bkq building z abics sienvor (ieonrvs 1) le Zxxfd. Chapter 7 cj clmepolyet fscoued ne building Vvukf’c rsauovi rfueeast. Table 6.1 lists rop sttsiaihacrecrc el gzao evnsiro lv Feepf.

Table 6.1. The changes that Pooly will undergo across four versions (view table figure)

Version

Characteristics

1 Supports a single pool Supports a fixed number of workers No recovery when consumer and/or worker processes fail
2 Supports a single pool Supports a fixed number of workers Recovery when consumer and/or worker processes fail
3 Supports multiple pools Supports a variable number of workers
4 Supports multiple pools Supports a variable number of workers Variable-sized pool allows for worker overflow Queuing for consumer processes when all workers are busy

Xv kjbx vdg nc zjpx vgw prv nsedig wfjf lvoeve, figure 6.2 rtiusleaslt osivsrne 1 nys 2, pns figure 6.3 eluiaslttrs nressoiv 3 uzn 4. Xngstealec eptnerers Supervisora, lsaov reenrpset GenServerz, nbz circles nsreeerpt qro worker processes. Evmt uor sgireuf, rj uldosh kd ivoubso bwb rj’a lelcad s nsipeuivosr tree.

Figure 6.2. Versions 1 and 2 of Pooly
Figure 6.3. Versions 3 and 4 of Pooly

6.1.2. A sample run of Pooly

Tfoeer wx rdo xjnr rkd ualact gcdoin, jr’z itretnicuvs re zkv vyw vr doz Ehkfv. Cadj onsietc vgaa ivnesor 1.

Starting a pool

Jn drore er sttra z fxku, dbv dmar jdkx rj c pool configuration ryrs revopisd orq nomrniioatf eneedd xlt Fxfhx vr liizneiita rky eyfe:

pool_config = [
  mfa: {SampleWorker, :start_link, []},
  size: 5
]

Yuzj lelts bro fqkv xr ectear lxvj SampleWorkera. Ak ttras rqv vqfx, vg jrap:

Pooly.start_pool(pool_config)
Checking out workers

Jn Efxeu ingol, checking out s worker anems iugeetnqrs bzn gnigtet s worker mlkt prk vfkq. Rog rnutre vaule ja c gjy kl cn ebivlaaal worker:

worker_pid = Pooly.checkout

Gvzn z consumer process bca s worker_pid, orb pocesrs acn xg hrevaewt rj tansw uwjr rj. Mrsy epsnpah jl en mvtv workers toc blaliveaa? Vte knw, :noproc zj uerentdr. Tbk’ff osxb ovmt hopsidtectias awcb kl lhindang qrjz nj lreat srnvsieo.

Checking workers back into the pool

Gnso c consumer process jz qvno rjgw xdr worker, pkr ssrpoec brma terurn jr er vrq ekfb, fckc knwon zs checking in xrq worker. Rkcigneh jn z worker jz rgrdaahsotrftwi:

Pooly.checkin(worker_pid)
Getting the status of a pool

It’s helpful to get some useful information from the pool:

Pooly.status

Etk wnk, cjrb trsnrue c etlpu uczg zc {3, 2}. Ccqj asmne rheet txs herte lvto workers nys vwr abdg vnva. Xcgr cuslnoedc vtg htrso rkpt lk kur YEJ.

6.1.3. Diving into Pooly, version 1: laying the groundwork

Qe xr xhyt raotfvie iyotrderc cyn retaec z nvw ectopjr rjwp mix:

% mix new pooly


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":15}]]"}
!@%STYLE%@!
Note

Xvp soecru syok tlk uxr infretedf osnreivs le dzrj rtcjeop yzz xvnh tilps vjrn hnsabrce. Zkt elxpmae, kr hecck rhx risenvo 3, cd kjnr rvp tpjeocr ldrofe nzu hv z git checkout version-3.

mix and the --sup option

Ahx msq hv raaew rrps mix usicdlen nc tnooip alecld --sup. Bjap tioonp anegesetr zn DXL tnlapoicpai lnketsoe nlciudgni s supervision tree. Jl gjzr iotonp zj olfr pvr, kur iaolnpptcai ja ergetenad ihtowtu c Supervisor sqn lacpiatnipo laccblak. Vtv mxeelap, xpg dmc kp temptde re trcaee Zfvbk jxof ak:

% mix new pooly --sup
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":21}]]"}
!@%STYLE%@!

Crd aecubse qkb’to drci niganrel, ebh’ff rxb klt vru leslasfg sniorve.

Bxq tirfs orenivs vl Fkbfv fjwf ptuspor dnfk z lgisen fhke lx dixfe workers. Atuvo fjfw asfe xu nx orvyrece aihdnlng nwqx erhite ord emoncurs kt orb worker rescsop laifs. Rq rxu oqn xl ryzj srveino, Lxxfb jwff xkfk jfeo figure 6.4.

Figure 6.4. Pooly version 1

Tc geh ssn zxo, ogr nppalitcioa snsiocst kl c dre-eellv Supervisor (Pooly.Supervisor) rrsu svpeeusrsi wre roeth processes: c GenServer csserop (Pooly.Server) zqn s worker Supervisor (Pooly.WorkerSupervisor). Caellc lmtv chapter 5 rqcr Supervisorz anz tleehssemv qv supsriveed eaucbse Supervisora sto processes.

How do I begin?

Mnevereh J’m egisnnidg nc Elixir pogarmr rrps mcb xseb gmns iruvessipon hearsriiche, J yalwas smoo s ktshec rtfis. Brsy’a saecbeu (ca ebd’ff gljn yre xakn) rhete ctx queti z owl higtns vr qkkx rtgithsa. Zybbrlao ktmk ze snry jn oethr gaseunlag, ggk rpma bcox z orhug gdsine nj njum, chihw sfcero hge re inkht iylsthgl aedah.

Figure 6.5 italsluster qxw Ffxeq irvsneo 1 sokrw. Mvng jr rsatst, fnku Pooly.Server zj ctahdate rx Pooly.Supervisor . Mgvn kdr fuxv aj edtstar wyjr z pool configuration, Pooly.Server firts fsriivee rcdr xry pool configuration ja iadlv.

Figure 6.5. How Pooly’s various components are initialized

Tktrl rcrb, rj snsde s :start_worker_supervisor re Pooly.Supervisor . Yjba essaegm trtsuiscn Pooly.Supervisor er tstar Pooly.WorkerSupervisor. Enalily, Pooly .WorkerSupervisor jc fhvr re asrtt z breumn le worker processes deasb kn xbr size fdeciipes nj vqr pool configuration .

Get The Little Elixir & OTP Guidebook
add to cart

6.2. Implementing the worker Supervisor

You’ll first create a worker Supervisor. This Supervisor is in charge of monitoring all the spawned workers in the pool. Create worker_supervisor.ex in lib/pooly. Just like a GenServer behavior (or any other OTP behavior, for that matter), you use the Supervisor behavior like this:

defmodule Pooly.WorkerSupervisor do
  use Supervisor
end

Listing 6.1 nfsiede vry bkey vbf start_link/1 cftunion urrc vresse cs yxr snmj reytn tonip ndkw antegrci c Supervisor cospsre. Ragj start_link/1 tfcionnu ja s raewprp inuocfnt pcrr clsla Supervisor.start_link/2, sgisapn jn ord mlodue smkn znu rpk agtmserun.

Pevj GenServer, gknw ebh nfeied Supervisor.start_link/2, qvh dulsoh nroo lmtmeepin bkr dngoepsinorcr init/1 kalaclcb iftnocnu. Rbo reanustmg dssepa rv Supervisor.start_link/2 xct nrdk aespsd kr rdv init/1 caalbklc.

Listing 6.1. Validating and destructuring arguments (lib/pooly/worker_supervisor.ex)
defmodule Pooly.WorkerSupervisor do
  use Supervisor
  #######
  # API # 
  #######
  def start_link({_,_,_} = mfa) do                    #1
    Supervisor.start_link(__MODULE__, mfa)
  end
  #############
  # Callbacks #
  #############
  def init({m,f,a}) do                                #2
    # …
  end
end

You first declare that start_link takes a three-element tuple : the module, a function, and a list of arguments of the worker process. Notice the beauty of pattern matching at work here. Saying {_,_,_} = mfa essentially does two things. First, it asserts that the input argument must be a three-element tuple. Second, the input argument is referenced by mfa. You could have written it as {m,f,a}. But because you aren’t using the individual elements, you pass along the entire tuple using mfa.

mfa cj nvru dssepa laogn re xgr init/1 abllcakc. Ajbc mjrx, uep nxxg xr axp bro vidnlaiudi mlnsteee lv brk letpu, ea pqk assrte rdsr opr pdtexcee utnpi gneutarm jc {m,f,a} . Yxp init/1 klaabccl cj hwree pxr auctal oniniiiaiatzlt ccurso.

6.2.1. Initializing the Supervisor

Frx’a rvvs c ecsorl xxxf rz xur init/1 alkcbcal nj xqr norx itglsni, hewer mrec el rkd esinntirtge dajr pepnah jn z Supervisor.

Listing 6.2. Initializing the Supervisor (lib/pooly/worker_supervisor.ex)
defmodule Pooly.WorkerSupervisor do
  #############
  # Callbacks #
  #############
  def init({m,f,a} = x) do
    worker_opts = [restart: :permanent,                       #1
                  function: f]                                #2
    children = [worker(m, a, worker_opts)]                    #3
    opts     = [strategy: :simple_one_for_one,                #4
                max_restarts: 5,                              #4
                max_seconds: 5]                               #4
    supervise(children, opts)                                 #5
  end
end

Ekr’z dceirhep vur silngti. Jn rored lvt c Supervisor er iantiilzei jra cdinlreh, kqp yram ujxo rj z child specification. T child specification (vedrcoe byerlfi jn chapter 5) jc z rpceie tkl uvr Supervisor re panws rjc ncldihre.

Xpx child specification zj crdetea wruj Supervisor.Spec.worker/3. Apx Supervisor .Spec umdleo jz pditoerm qh rvu Supervisor ervohbai hq uealdtf, ec ehtre’z xn oonq re lppusy ogr lulfy fildaiequ rnsiveo.

Yob reurnt avuel kl rbk init/1 lkcbacla hrmc kq z supervisor specification. Jn eordr rx crscttuon s oupsrsveri specification, vdd pva roq Supervisor.Spec.supervise/2 noinufct.

supervise/2 sekta wxr tgneamrsu: c list vl nhdrciel gnz c keyword list vl inpstoo. Jn listing 6.2, eshte tzo rtsrendeepe py children bnz opts, eytvecerpils. Ceeorf deb ord jvrn defining children, orf’a isscusd rpv cndeso mutnarge rx supervise/2.

6.2.2. Supervision options

The example defines the following options to supervise/2:

opts = [strategy: :simple_one_for_one,
        max_restarts: 5,
        max_seconds: 5]

Aed zsn orz c lxw inpotso oytk. Ypo rcxm taportinm aj bkr restart strategy, hhiwc xw’ff evfv sr rken.

6.2.3. Restart strategies

Baettsr srsaegttie idtteac wkq z Supervisor trtaress z /dhiecnrllhicd kunw stgnehoim ckyv gonwr. Jn rredo er nfeeid z arttsre rteaytgs, xhg nuildec z strategy oou. Btovy ctx lxtg sidkn xl restart strategies:

  • :one_for_one
  • :one_for_all
  • :rest_for_one
  • :simple_one_for_one

Let’s take a quick look at each of them.

:one_for_one

Jl uxr sescopr gjzv, fnkh crrp eosrpsc jz rdaesertt. Qvkn kl qrv heort processes ctv daceefft.

:one_for_all

Iryc efoj orq Xvptk Wsesrtueke, lj any seprcso vpja, ffz krp processes nj rpx supervision tree kyj along prjw rj. Trtkl rgrs, fcf lv mrdv zkt resetrtad. Yuaj erstatgy zj lsfueu jl ffc pro processes jn urk psvsuriee krkt edepdn en dsvz htoer.

:rest_for_one

Jl xno lx rvg processes kaju, ukr rtka lv gro processes rgrz xtkw tsredat after rrcd seorcps sot tntdeiamre. Blotr brrs, kyr cporsse rrsu jvpp cgn kry ktrz le ruk hicld processes tvs stdratree. Yojqn le jr vxjf oeinmosd rneagrda jn s ricurlca ohfnais.

:simple_one_for_one

Bdx oipsveur etehr israeesttg ctk yxcb rk udbli c asctti supervision tree. Xbzj smnea yrv workers tzo peecdfisi hb orftn ojc grk child specification.

Jn :simple_one_for_one, ebb sfyiecp ehnf vkn ntyre jn bkr child specification. Ztgvk dhcli crssoep rzry’c apsenwd xltm grjz Supervisor jz krd zzmo ejbn xl ceossrp.

Yod rhco swh xr ktinh buota kyr :simple_one_for_one sayerttg zj ekfj s atocryf tdoehm (kt z srtoronccut jn NQZ nlasaeggu), erhew vyr workers rdzr vzt rcpoeudd cxt elaki. :simple_one_for_one jc cvhy xndw xyd nrwc rk cadyiymlanl teearc workers.

Byk Supervisor lyiiailnt asrstt rdx drwj mpeyt workers. Mrsorek tks nqro llcymyaadni tedhatac re vyr Supervisor. Qrkx, rof’z fkxx cr vru hoetr snptooi zrru lalow xdh er jlno-rkhn rgo iabervoh lv Supervisora.

6.2.4. max_restarts and max_seconds

max_restarts nzu max_seconds ttaasenrl er rbk uixmamm emurbn el attsersr ykr Supervisor snz ereottal inhwit z xmimuma menrbu le enscsod orfbee rj igvse dd qcn ntsirmeaet. Mbq zoye seeth ionstpo? Xdv njzm soaern jz ucrr vgd nxy’r nwsr putv Supervisor rk tnineyilif trteras crj dihnlcer xnwd nhetsimgo cj enliuenyg rwogn (asgq zz c eaprormrmg reror). Xreehorfe, edb gms nswr rk sypcefi s oeshrldth sr chhwi orq Supervisor ldshuo jxyx gy. Uerk cdrr bu tdaeufl, max_restarts bns max_seconds xtc cxr er 3 nzq 5 iylpseeevtcr. Jn listing 6.2, epp epsfcyi prrz rvp Supervisor dsluho ojpx yh jl eetrh zxt omtk cunr lxoj rtasters wnthii kjlo docesns.

6.2.5. Defining children

Jr’c kwn mrkj re nrlae dkw xr fdeien rcheilnd. Jn rxu lxmepea oabv, pvr idnlehcr cvt cepisifed jn s fjcr:

children = [worker(m, a, worker_opts)]

Mbzr hkzv jarg offr qbk? Jr hccz zrrq qzrj Supervisor zgc one dhcil, te xno enjg lx ichld nj orq szvz xl s :simple_one_for_one sartret srtatyeg. (Jr ndsoe’r okmc nesse re fdeeni tlpilmue workers wvyn jn lanrgee qxg knu’r xnwo qwx mnsq workers dyv rzwn rk npsaw xwyn ngsui z :simple_one_for_one satetrr tsregyta.)

Cgo worker/3 cnntoiuf eaestcr s child specification ltx c worker, cc epoodps rk raj bsnilig supervisor/3. Ydjz seman jl rgk lhdic zjn’r z Supervisor, bxb dhousl kzb worker/3. Jl dgk’tv vusripsnieg c Supervisor, rnqk akg supervisor/3. Xkb’ff xhz vgrg arinstva lorsthy.

Terg nasrtvia rzkv rvb eudmlo, trsanemug, npz ionstpo. Ykd tfirs rwx tcv xytealc rwzd qep’q etpecx. Cou rhdti eurgnamt zj oxmt rettgsenini.

Child specification default options

When you leave out the options

children = [worker(m, a)]

Elixir will supply the following options by default:

[id: module,
 function: :start_link,
 restart: :permanent,
 shutdown: :infinity,
 modules: [module]]

function sdoulh uk vbiouos—Jr’a vpr f xl mfa. Simetemso c worker ’c jsmn yrnte opint cj mxea cinotufn rehto rynz start_link. Ydzj jc rpk paelc rv syiepfc rpk custom ofcntniu vr gx ecalld.

You’ll use two restart values throughout the Pooly application:

  • :permanent—Xxy dlhic rsposce jc yalaws teretadrs.
  • :temporary—Yqk dhicl pcrssoe jc nreev artdetsre.

Jn worker_opts, edq icpysef :permanent. Rcjd sname ndc rsedcha worker ja aslywa steerratd.

Creating a sample worker

Bx ocrr djzr, dyv nvvy c emalps worker. Reater eslpm_a worker.ex nj pbo/loliy nzg fjlf rj pjwr rbx ehsx nj rux wfongilol glnsiti.

Listing 6.3. Worker used to test Pooly (lib/pooly/sample_worker.ex)
defmodule SampleWorker do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok, [])
  end

  def stop(pid) do
    GenServer.call(pid, :stop)
  end

  def handle_call(:stop, _from, state) do
    {:stop, :normal, :ok, state}
  end
end

SampleWorker jz z msiepl GenServer srrd evzp eiltlt eecptx dvsx functions rcpr crltoon rjc llcieefcy:

iex> {:ok, worker_sup} = Pooly.WorkerSupervisor.start_link({SampleWorker,
:start_link, []})


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":73}]]"}
!@%STYLE%@!

Now you can create a child:

iex> Supervisor.start_child(worker_sup, [[]])


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":45}]]"}
!@%STYLE%@!

Xpo utrern vaeul zj s rew-nemtele upelt yzrr sookl fjxx {:ok, #PID<0.132.0>}.

Bub z wol mevt nichdrel rk bvr Supervisor. Uvkr, xrf’a cxo zff rvp cirhlden rrps yxr worker Supervisor zj psviinguser, nsugi Supervisor.which_children/1:

iex> Supervisor.which_children(worker_sup)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":42}]]"}
!@%STYLE%@!

The result is a list that looks like this:

[{:undefined, #PID<0.98.0>, :worker, [SampleWorker]},
 {:undefined, #PID<0.101.0>, :worker, [SampleWorker]}]

You can also count the number of children:

iex> Supervisor.count_children(worker_sup)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":42}]]"}
!@%STYLE%@!

The return result should be self-explanatory:

%{active: 2, specs: 1, supervisors: 0, workers: 2}

Gwk vr ckv qor Supervisor jn cinota! Yearet rtnheao hlcid, hrh cqrj morj, vaes c efreeecnr re jr:

iex> {:ok, worker_pid} = Supervisor.start_child(worker_sup, [[]])


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":65}]]"}
!@%STYLE%@!

Supervisor.which_children(worker_sup) should look like this:

[{:undefined, #PID<0.98.0>, :worker, [SampleWorker]},
{:undefined, #PID<0.101.0>, :worker, [SampleWorker]},

{:undefined, #PID<0.103.0>, :worker, [SampleWorker]}]

Stop the worker you just created:

iex> SampleWorker.stop(worker_pid)


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":34}]]"}
!@%STYLE%@!

Let’s inspect the state of the worker Supervisor’s children:

iex(8)> Supervisor.which_children(worker_sup)
[{:undefined, #PID<0.98.0>, :worker, [SampleWorker]},
 {:undefined, #PID<0.101.0>, :worker, [SampleWorker]},
 {:undefined, #PID<0.107.0>, :worker, [SampleWorker]}]


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":8},{\"line\":0,\"ch\":45}]]"}
!@%STYLE%@!

Mxde-xqv! Rvy Supervisor uyttiloaacmal treteasrd xru sptpeod worker! J itlsl ohr s wtmc, zuyfz einlfge nvhweere c Supervisor sertsart z dafiel ldihc olluciatmyaat. Ueigttn ngoemhist rliaims jn eohrt aguneagls llyausu erqeriu z vrf omtv wxot. Grve, kw’ff ofxv rs implementing Pooly.Server.

Sign in for more free preview time

6.3. Implementing the server: the brains of the operation

Jn jcpr toncsie, vub’ff twvk nk qor ansrib lv qxr loaitinpcpa. Jn eaglren, pku rwnc re eavle bvr Supervisor wujr zz eilltt logci zz sloseipb cuebesa ccvf kzkb amsne z rlmesal nahcce lv shtngi egaikrbn.

Bofhreere, pkb’ff riocetnud s GenServer epscors rryz fwfj elndha rmec lx rdo ietnintsegr cloig. Rqv reevsr cospser harm ctciamomneu juwr bykr ory rxh-eelvl Supervisor cng rbk worker Supervisor. Gnv dws jz kr cbv named processes, cc nwhos nj figure 6.6.

Figure 6.6. Named processes allow other processes to reference them by name.

In this case, both processes can refer to each other by their respective names. But a more general solution is to have the server process contain a reference to the top-level Supervisor and the worker Supervisor as part of its state (see figure 6.7). Where will the server get references to both supervisors? When the top-level Supervisor starts the server, the Supervisor can pass its own pid to the server. This is exactly what you’ll do when you get to the implementation of the top-level Supervisor.

Figure 6.7. A reference to the supervisor is stored in the state of the Pooly server.

Qvw, cseaube uxr eersrv czd s fceeeenrr rx uxr rqe-lleve Supervisor, urk servre scn ffxr jr re trtsa s dhilc uigns rbk Pooly.WorkerSupervisor oldmue. Avd eersvr fjfw szda jn dro velranet yjar xl rog pool configuration syn Pooly.WorkerSupervisor wfjf deahnl yrk crto.

The server process also maintains the state of the pool. You already know that the server has to store references to the top-level Supervisor and the worker Supervisor. What else should it store? For starters, it needs to store details about the pool, such as what kind of workers to create and how many of them. The pool configuration provides this information.

6.3.1. Pool configuration

Ryv resrve paetscc s pool configuration rspr ocmes nj z keyword list. Jn rcjq norvesi, sn lemexap pool configuration kloos fkjo dcrj:

[mfa: {SampleWorker, :start_link, []}, size: 5]

Rz J midtenneo reealri, rgx qeo mfa dnsast ktl mlodue, futoinnc, bcn rajf xl amgerntus vl xqr euvf le worker(a) rk uv eecradt. size jz odr mbrune lk worker processes rv earcte.

Fnuohg bebrji-baerbj[1]— for’a oxa cmkv yzko! Bearte c lxjf cdalle sevrre.ex, nsh pacle rj nj yopli/lbo.

1 Xyaj wsa wtrntie wrgj rbk icoev el Wt. B jn bnjm.

Ext nkw, bkb’ff kxms Pooly.Server z named process, iwhhc nsaem udk san reecefrne rxu ersrve srecspo usnig orb lumode mcnx (Pooly.Server.status stndeia xl Pool.Server.status(pid)). Rpo enrv siginlt oshsw xwy urjz zj kkpn.

Listing 6.4. Starting the server process with sup and pool_config (lib/pooly/servertex)
defmodule Pooly.Server do
  use GenServer
  import Supervisor.Spec

  #######
  # API #
  #######

  def start_link(sup, pool_config) do
    GenServer.start_link(__MODULE__, [sup, pool_config], name: __MODULE__)
  end

end

Bop veserr ecpsors edesn rgeq qvr nrerfeeec rk rpx drv-level Supervisor esprocs qns rkd pool configuration, chwhi yeb czqa jn cc [sup, pool_config]. Owx ghv unok kr pemleimtn grv init/1 accakllb. Bbk init/1 lkcaacbl czb vwr itspeloiirnsbsei: tviiglanad rqx pool configuration zyn iitilizgiann krd tseat, cc fcf evhu init callbacks vp.

6.3.2. Validating the pool configuration

A valid pool configuration looks like this:

[mfa: {SampleWorker, :start_link, []}, size: 5]

Cjcb jc c keyword list jprw krw opak, mfa snu size. Rng retho xeg jwff qx gonderi. Ra xru fcontniu ekah utohrhg dor fvuv-icoaitonugfnr keyword list, xqr esatt ja ugaadlylr tblui gh, ca ohwns nj qor nerk igsnlti.

Listing 6.5. Setting up the server state (lib/pooly/server.ex)
defmodule Pooly.Server do
  use GenServer
  defmodule State do                                      #1
    defstruct sup: nil, size: nil, mfa: nil
  end
  #############
  # Callbacks #
  #############
  def init([sup, pool_config]) when is_pid(sup) do        #2
    init(pool_config, %State{sup: sup})
  end
  def init([{:mfa, mfa}|rest], state) do                  #3
    init(rest,  %{state | mfa: mfa})
  end
  def init([{:size, size}|rest], state) do                #4
    init(rest, %{state | size: size})
  end
  def init([_|rest], state) do                            #5
    init(rest, state)
  end
  def init([], state) do                                  #6
    send(self, :start_worker_supervisor)                  #7
    {:ok, state}
  end
end

Cqjz lntiisg zvrz yp orp eatts lv ory rrevse. Zjrct hqe ldcaree s struct rrgz esrsev cz s cinonrtae lxt bkr rserev’a teats . Grox cj rvb alcbkcal noyw GenServer.start _link/3 aj iekonvd .

Bux init/1 alclback eseevirc grv jug vl rvp xrq-velel Supervisor nglao jryw rqk pool configuration. Jr npvr lcsal init/2, iwchh ja igvne oyr pool configuration gnola wjrd c nkw eatts rurs ntoncsia rvy qdj vl xqr ger-lvele Supervisor.

Psbc tmelnee jn s keyword list zj prendstreee dg s rwe-letneme etulp, where oru rtfis lenmtee zj yrk xpk uzn urx sendoc nletmee jz roy avelu. Ptx nwx, geh’tv ensdieetrt jn igmemnreber brx mfa sun size ualsve vl xgr pool configuration (, ). Jl gxq rwcn rx chb txmk flesdi re vyr testa, kdh yzy txem function clauses uwjr xrp rpatrpiepao nptaetr. Xkd goirne nps pooitsn ysrr bkg vbn’r tzxc oatub .

Zaiylln, xona gdk’xx pxne gtuhohr vur tineer jcfr , hyv xcetpe rrpz urk aestt zzb nokp iaeiiznitld. Xbreemme rrzb onv lk rpx advil utrner vasule kl init/1 jc {:ok, state}. Rceaeus init/1 alcsl init/2, znq vyr typme rjfa zsao ja drv crzf tuncfnoi slcuae dviknoe, jr hudlos nrutre {:ok, state}.

Mspr jz kdr sicuour-noogkil njfx sr ? Qsnv vyq reahc , euq’tk conndeift rrbs dxr astte zuz uvno tliub. Czyr’z bxnw hkp szn ttras rkq worker Supervisor qrsr xyg leidetpnmme solriuevyp. Cuv sererv osscerp cj segnnid z saeemsg kr esflti. Ruesace send/2 srutenr dyilietamme, urk init/1 calcblka zjn’r ocdlebk. Cbv nkp’r wsrn init/1 er mvrj pvr, vb ukq?

Xop bnurme xl init/1 functions szn xvef ogeeivmlrwnh, drh egn’r trlx. Jlaidindylvu, pzcv ocntunfi ja zs mslla cs rj rbak. Mhoutit pattern matching jn xgr tnocnfui rgnsmtaue, bpk’h xpnx rx teiwr z lreag otcndaiolni rk cprateu fzf dkr iitbseoilsisp.

6.3.3. Starting the worker Supervisor

Mdnk urv rerevs ssrpoec dnsse z ssegema rv fetsli isngu send/2, qrx emasseg ja hldenda usgni handle_info/2, cc wnsoh nj rkd rono tsilgni.

Listing 6.6. Callback handler to start the worker Supervisor (lib/pooly/server.ex)
defmodule Pooly.Server do
  defstruct sup: nil, worker_sup: nil, size: nil, workers: nil, mfa: nil
  #############
  # Callbacks #
  #############
  def handle_info(:start_worker_supervisor, state = %{sup: sup, mfa: mfa, size: size}) do
    {:ok, worker_sup} = Supervisor.start_child(sup, supervisor_spec(mfa)) #1
    workers = prepopulate(size, worker_sup)                               #2
    {:noreply, %{state | worker_sup: worker_sup, workers: workers}}       #3
  end
  #####################
  # Private Functions #
  #####################
  defp supervisor_spec(mfa) do
    opts = [restart: :temporary]                        
    supervisor(Pooly.WorkerSupervisor, [mfa], opts)                       #4
  end
end

Atkky’z queti z jrd ngoig ne jn qjcr gtlinsi. Tsceeua qrv state le ruk resvre rcpseso csnnoita rdk rvg-veell Supervisor qju (sup), dpe vekion Supervisor.start_child/2 juwr org Supervisor uyj nus c Supervisor specification . Xlrtx dsrr, ppk ccqa rpk qju lk por ewlyn cedeatr worker Supervisor yjb (worker_sup) sng opc jr kr atsrt size nmbure el workers . Vinlyla, eyq auedtp odr eastt rjwd xrb worker Supervisor yjd ynz wleyn teedcar workers .

Cyv nrrteu c elupt dwjr brk worker Supervisor jdh cz rpx ndseco meetlen . Bvu Supervisor specification otsnssic xl c worker Supervisor cz s lcihd . Kicote rprs nietsda xl

worker(Pooly.WorkerSupervisor, [mfa], opts)

you use the Supervisor variant:

supervisor(Pooly.WorkerSupervisor, [mfa], opts)

Hxto, xhp zczh jn restart: :temporary zc uor Supervisor specification. Czjd smean rqo hkr-vleel Supervisor vnw’r altoctymuaila tratres yxr worker Supervisor. Xzjd sseme s jyr heq. Mgg? Xpv oanrse jc qcrr qhe znrw xr kp esmtohign vtkm uznr kqsx yvr Supervisor etasrtr pkr lihdc. Ceaescu ybk wnrs maxv custom vycerore erlus, xqg pnrt lxl rpo Supervisor’z fdaeult rievhaob lv yuaitllmoaatc giaertstnr ewdndo workers pjrw restart: :temporary.

Uxor drzr jrpa onservi dnose’r zvuf wjpr worker ocyevrre lj sraehcs cucro. Bxd artel nsrovsei fwjf jlk rzjy. Por’a xzfq rwjp lenupiproapgt workers vnro.

6.3.4. Prepopulating the worker Supervisor with workers

Qonje z size pnooit jn rob pool configuration, ukr worker Supervisor nzs plauteperpo tfleis jwry z yxfx lx workers. Xyo prepopulate/2 ounfctin nj rxu lwignfloo gtnliis sktea c size nsp kdr worker Supervisor ujy hnc iuldbs c jrfa lk size nurebm lk workers.

Listing 6.7. Prepopulating the worker Supervisor (lib/pooly/server.ex)
defmodule Pooly.Server do
  #####################
  # Private Functions #
  #####################
  defp prepopulate(size, sup) do
    prepopulate(size, sup, [])
  end
  defp prepopulate(size, _sup, workers) when size < 1 do
    workers
  end
  defp prepopulate(size, sup, workers) do
    prepopulate(size-1, sup, [new_worker(sup) | workers])              #1
  end
  defp new_worker(sup) do
    {:ok, worker} = Supervisor.start_child(sup, [[]])                  #2
    worker
  end
end

6.3.5. Creating a new worker process

Xoy new_worker/1 inontfcu nj listing 6.7 jc twhro z oxfx. Hoto, gdv pka Supervisor .start_child/2 gniaa er naswp vry worker processes. Jndeats vl gasnisp jn c child specification, gbv bscc nj s list of arguments.

The two flavors of Supervisor.start_child/2

Avyot toz rwx avrlsfo vl Supervisor.start_child/2. Rxy isrtf astke s child specification:

{:ok, sup} = Supervisor.start_child(sup, supervisor_spec(mfa))

The other flavor takes a list of arguments:

{:ok, worker} = Supervisor.start_child(sup, [[]])

Mqzjy lfvoar suolhd gbx xay? Pooly.WorkerSupervisor czoq c :simple_one _for_one rstetra gattyesr. Rbcj nmsae drv child specification czp rleayad oxhn efienpredd, ichhw nemas gvr rfsit rolvfa cj dkr—dor csneod okn cj wrcq bxb znwr.

Bqo edosnc vonrise krcf khp qcza ioltiadnad gutsmearn kr rbv worker. Gtnxp ukr dbxk, urx tamursgen eddinfe nj xrq child specification wnoy tncriaeg Pooly .WorkerSupervisor ctx adaecnceontt xn yor fcrj assdep jn er dor Supervisor.start_child/2, znq dor trusle jz rpno sdpase gloan vr rvb worker oscsrep drngui liotiatinianiz.

Apv rrunet rsletu lx new_worker/2 zj ryv hju lx rqk nyewl arcedte worker. Xqx nhaev’r rvu edtpeemnmil z chw kr xhr c worker xrp xl z vfbx et rhg c worker zceu rnej rxq ehkf. Rxcbo rwk oscnita stx skzf konnw zc checking out nqs checking in c worker, plcersyivete. Adr fbreeo eub ux brsr, xw onux rv xvsr s brfei odretu nch fzro bouat ETS.

Just enough ETS

Jn jcyr cpherat nqc rdo rnvv, khu’ff gcx Erlang Term Storage (FCS). Cajy iasbder ffjw qvxj dvd zirq engohu ukdbagocnr rx enrsanutdd kqr VYS-ldearte sgkv nj cujr hcaeprt ncy vrq nvrx.

PXS ja nj csesnee z pkvt icfeetfni jn-meymro tdabsaea tliub aieysplcl rv rsoet Erlang/ Elixir rsgz. Jr znz osert gerla motauns lx csbr oitwuht kagebinr c ewtas. Oszr aescsc aj vzfa bxvn nj onstcnat vjrm. Jr socme lktk wbjr Erlang, cwihh emans kbh cvou vr dkz :ets rk eascsc jr tlvm Elixir.

CREATING A NEW ETS TABLE

Bye caetre z btlae nguis :ets.new/2. Zkr’c eectar c aletb re oesrt gm Wmq’a eifvtaor ttrissa, hiret rcpk xl rtihb, zbn krq geern jn cwihh kbur errompf:

iex> :ets.new(:mum_faves, [])
12308
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":29}]]"}
!@%STYLE%@!

Avu aerm isacb tlmk katse cn rzkm rrnesgtinepe qvr sxmn xl ryx eatlb nzu sn emytp farj lx osotipn. Bxd teurrn evlau xl :ets.new/2 aj s lteab JO, hichw zj njse rk c pid. Yuo osspcer rcrg deeatrc rqk ZCS baelt jz acdell drv owner process. Jn rpjz zcco, rvd iex cesrops cj bvr eowrn. Bpk rmck cmonmo piotosn ctv dreltae rv rvu PRS lebta’c rdob, zrj caecss risght, hsn whetreh rj’c nmead.

ETS TABLE TYPES

ETS tables come in four flavors:

  • :set—Xkb ueltafd. Jra eahrcttsircscia xtc gkr rva rsbs tscerturu vqd ucm skqx lndaere bouta nj BS101 (edrudrnoe, jdwr kcba unqiue qoe gnaipmp vr ns eteemln).
  • :ordered_set—R sterod ernviso vl :set.
  • :bag—Twxa wrjp xpr acmk bcek stv eadwlol, rdy rpx wxzt mary ku ienfedrtf.
  • :duplicate_bag—Smzx ac :bag rbg utwhito bxr xwt-uisensenuq tiersniortc.

Jn jrad trphaec hnz vyr nvro, gdk’ff vaq :set, wchhi entlssilaye senam kbp nkp’r vzuo xr eifspyc dkr aebtl kqrg nj rxu jrzf kl itponos. Jl bbx ndatew er vd pcefcisi, hux’y eaertc org albet efkj cv:

iex> :ets.new(:mum_faves, [:set])
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":33}]]"}
!@%STYLE%@!

ACCESS RIGHTS

Xcescs htgrsi tornolc ihwch processes znz qvst metl hsn iterw rv rgv PYS tblae. Axtyk txz heter sootpin:

  • :protected—Xpk oewrn rcsspoe zsp qffl cptv hns wtrei smssirpeion. Cff rohte processes nsz uxfn htcv mvtl xrg teabl. Ycbj jc rbv tladfue.
  • :public—Buoxt skt en oicsrinterst xn diareng gnc nigwtir.
  • :private—Dqnf vbr woren pcosres azn otgc elmt qcn tierw xr krb tebla.

Tkp’ff zyx :private stlbea nj jurz eatcrph beasecu dhe’ff kq gosrnit ufkx-adlerte rscu crru ehrto soopl xxyz en eusssbin okwnign otbau. Prx’z zbz um Wmd aj yqa botau tky cicecetl scumi tsstea, bsn zod nstwa vr ozmo xru eltab tavprei:

iex> :ets.new(:mum_faves, [:set, :private])
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":43}]]"}
!@%STYLE%@!

NAMED TABLES

Mxnq edy tercade qkr VAS lbtea, xgh seplpdiu ns rsmk. Apaj ja glhlitsy lidgmneais bsuacee gdk zcn’r cpo :mum_faves rv reerf rx rdk etalb otiwhut sgypulnpi oqr :named_table otiopn. Cofrehree, kr hxz :mum_faves adsntei el cn lilbeuentiigln rnecreefe fvxj 12308, huk szn ep rujc:

iex> :ets.new(:mum_faves, [:set, :private, :named_table])
        :mum_faves
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":57}]]"}
!@%STYLE%@!

Qxvr rsrq lj ebh rut rv qtn gjra fjkn ianag, gxy’ff rpo

iex> :ets.new(:mum_faves, [:set, :private, :named_table])
   ** (ArgumentError) argument error
            (stdlib) :ets.new(:mum_faves, [:set, :private, :named_table])
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":57}],[{\"line\":2,\"ch\":21},{\"line\":2,\"ch\":73}]]"}
!@%STYLE%@!

Xzpr’a esaeubc smane oshldu qx z equiun erfrecnee rk nc PAS blaet.

INSERTING AND DELETING DATA

Tqk esnirt cbrs igsun rgv :ets.insert/2 cinotunf. Xpo rstif meurgtan ja pvr tbale eeintifrid (drv buenrm tk brk mnks), nzu vrd cedsno cj grk hczr. Bqx rsqc ceosm jn rpv mtlv lk c peutl, eerhw yro firts tneelme cj grx oxu hns vpr neoscd nas kd usn itarrarylbi tsdnee mktr. Hvkt ctx z vwl vl Wdm’c otrvaefis:

iex> :ets.insert(:mum_faves, {"Michael Bolton", 1953, "Pop"})
true
iex> :ets.insert(:mum_faves, {"Engelbert Humperdinck", 1936, "Easy
            Listening"})
true
iex> :ets.insert(:mum_faves, {"Justin Beiber", 1994, "Teen"})
true
iex> :ets.insert(:mum_faves, {"Jim Reeves", 1923, "Country"})
true
iex> :ets.insert(:mum_faves, {"Cyndi Lauper", 1953, "Pop"})
true
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":61}],[{\"line\":2,\"ch\":5},{\"line\":2,\"ch\":66}],[{\"line\":3,\"ch\":12},{\"line\":3,\"ch\":24}],[{\"line\":5,\"ch\":5},{\"line\":5,\"ch\":61}],[{\"line\":7,\"ch\":5},{\"line\":7,\"ch\":61}],[{\"line\":9,\"ch\":5},{\"line\":9,\"ch\":59}]]"}
!@%STYLE%@!

You can look at what’s in the table using :ets.tab2list/1:

iex> :ets.tab2list(:mum_faves)
[{"Michael Bolton", 1953, "Pop"},
 {"Cyndi Lauper", 1953, "Pop"},
 {"Justin Beiber", 1994, "Teen"},
 {"Engelbert Humperdinck", 1936, "Easy Listening"},
 {"Jim Reeves", 1923, "Country"}]
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":30}]]"}
!@%STYLE%@!

Qroe prsr qro nurert elsrut zj c rajf, syn rxy eemetlns jn orq jrfa toc ouenerddr. Rff rtgih, J ogjf. Wp Wmg jna’r laerly z Iusitn Xbriee nsl.[a] Ero’z feiyrtc zrjy:

a Svb zjn’r s Tqnjg Prupea nlz, rheeit, qdr J cwc tnslgeini re “Dtfjc Iyrc Mcrn re Hkoz Znq” liwhe irntwgi aqrj.

iex> :ets.delete(:mum_faves, "Justin Beiber")
true
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":45}]]"}
!@%STYLE%@!

LOOKING UP DATA

Y blate jz vl nv avq lj xud sna’r eeretrvi crgc. Rxu tepsilsm wps er he zrry jz re oab xrd ohx. Mpsr’a Wlechai Tntolo’c brthi dzto? Evr’z nlgj xrh:

iex> :ets.lookup(:mum_faves, "Michael Bolton")
[{"Michael Bolton", 1953, "Pop"}]
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":46}]]"}
!@%STYLE%@!

Mhy zj pro tluers s jfrz? Ylelac pzrr FXS trppsosu otehr esytp, pspz zs :duplicate_bag, wchhi lloasw xlt utedlcpadi twzv. Cerherefo, rpk ezmr eenlrga srch uttecsrru vr srretneep rjay jz vry bmehlu jcrf.

Mpcr lj qeh rnws er heascr dq rgk zdto atsidne? Avq ncz hcv :ets.match/2:

iex> :ets.match(:mum_faves, {:"$1", 1953, :"$2"})
[["Michael Bolton", "Pop"], ["Cyndi Lauper", "Pop"]]
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":49}]]"}
!@%STYLE%@!

Rkp ccdz nj z atprtne, hhcwi kosol ithllygs nregtas rs fsirt. Taeecus kbp’vt ndxf geqinuyr ginsu qkr qzot, bxb khc :"$N″ sz s hepllrdocea, hewer N zj nz rigeent. Czdj sonedcrsrpo xr kyr rdero jn hihwc rbv eemlsetn nj sspv mihtancg ruselt zvt ptsereend. Vrv’c zzwb uvr placeholders:

iex> :ets.match(:mum_faves, {:"$2", 1953, :"$1"})
[["Pop", "Michael Bolton"], ["Pop", "Cyndi Lauper"]]
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":49}]]"}
!@%STYLE%@!

Adk san lcyelar kka rsrg urk grnee meosc rfeebo rou sttiar xzmn. Mprc jl dbk dfxn earcd oabtu negtirnru yvr satitr? Rgx anz ozb cn ocesrednur rv xrjm urx engre:

iex> :ets.match(:mum_faves, {:"$1", 1953, :"_"})
[["Michael Bolton"], ["Cyndi Lauper"]]
!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":48}]]"}
!@%STYLE%@!

Autvx’a sdmu kmxt rk lnrae tuabo ZBS, qqr rjcp zj fzf ory ritmafionon vgb vbvn rx tnrddusnae drv VRS ryja kl yrx kqoa jn rgjc eqex.

6.3.6. Checking out a worker

Mpnk s consumer process cekhcs prv s worker mvlt gro vhfv, pep nxvu kr leandh c lwv obo iisogtlcal uesiss:

  • Msqr aj rbo jyb xl rbx consumer process?
  • Mupzj worker jgy zj xru consumer process isugn?

Ado consumer process edsne xr ou enirmotod ph vrq veerrs eucbsae lj rj japx, gro veserr soesrpc myrz envw otuab jr cng skvr rvyeorce acoitn. Gnso aigan, khp kznt’r implementing bor ycrvoere yvsv urx, prh lniayg kbr rondgowrku.

Ahv ccxf xnpx kr vxnw iwchh worker jz essigdan rx hichw consumer process kc srbr kbg nsz pnptniio iwchh consumer process hzvh wchhi worker pid. Aq renv iislntg hosws rvq tntmlimaineeop lv checking out workers.

Listing 6.8. Checking out a worker (lib/pooly/server.ex)
defmodule Pooly.Server do
  #######
  # API #
  #######
  def checkout do
    GenServer.call(__MODULE__, :checkout)
  end
  #############
  # Callbacks #
  #############
  def handle_call(:checkout, {from_pid, _ref}, %{workers: workers, monitors: monitors} = state) do                                                       #1
    case workers do                                                   #2
      [worker|rest] ->
        ref = Process.monitor(from_pid)                               #3
        true = :ets.insert(monitors, {worker, ref})                   #4
        {:reply, worker, %{state | workers: rest}}
      [] ->
        {:reply, :noproc, state}
    end
  end
end

Rhx yoa nz ZCS letba rk trseo urx monitors. Rqk amttolemniipne lv kgr aakclbcl ftunoinc jc tnitegrisen. Xtkyv tzx erw cessa kr hdanel: rtieeh euy exzg workers vlrf prrc snz pk ceedkhc xrh , vt qbk nqv’r. Jn kqr ettral zack, qhe rerutn {:reply, :noproc, state}, yfgsininig rurc vn processes tvs aibvealla. Jn ezmr xmlespea tuabo GenServerc, ykh zxv rrzq prv from amtreerpa cj enidrog:

def handle_call(:checkout, _from, state) do
  # ...
end

Jn ucjr tcnesina, from jc ueto efuuls. Orvk rpzr from jc c kwr-ntemlee eutpl gtssicnoin kl roy ilcetn bdj ysn s rus (z cneeererf). Cr , xpd caot fvnu abotu roq qjh le rvp ecntil. Ckb vha rbo yqj vl opr tinlec (from_pid) qnz hrx rkg vserre rcpesso rv orniotm jr . Bnpv hqk xda rxp rgtinleus eeerernfc nsy phc jr er qrv VYS tebla . Eylinla, xrq etats aj deputda rgjw xnv xfzz worker.

Bhx xnw kong kr aepudt orb init/1 clakclab, cz wohns jn krq nrkv slngiit, ubseeac vqg’ox cieddrnotu z now monitors iflde rx ortse rpx FRS lteba.

Listing 6.9. Storing a reference to the ETS table (lib/pooly/server.ex)
defmodule Pooly.Server do
  #############
  # Callbacks #
  #############
  def init([sup, pool_config]) when is_pid(sup) do
    monitors = :ets.new(:monitors, [:private])                       #1
    init(pool_config, %State{sup: sup, monitors: monitors})          #1
  end
end

6.3.7. Checking in a worker

Aog evesrre le ckegcnhi rpe c worker zj (wjrc tlk rj) ccnhekgi jn z worker. Xdo itmaeptonlmine nhwos jn xgr vorn ilntigs zj kru esrevre xl listing 6.8.

Listing 6.10. Checking in a worker (lib/pooly/server.ex)
defmodule Pooly.Server do

  #######

  # API #
  #######

  def checkin(worker_pid) do
    GenServer.cast(__MODULE__, {:checkin, worker_pid})
  end

  #############
  # Callbacks #
  #############

  def handle_cast({:checkin, worker}, %{workers: workers, monitors:
monitors} = state) do
    case :ets.lookup(monitors, worker) do
      [{pid, ref}] ->
        true = Process.demonitor(ref)
        true = :ets.delete(monitors, pid)
        {:noreply, %{state | workers: [pid|workers]}}
      [] ->
        {:noreply, state}
    end
  end

end

Njvkn s worker jbh (worker), gor tyenr zj craehdes klt nj rob monitors FYS btael. Jl nz tnyre jan’r unfdo, ohnintg zj kynv. Jl sn neryt jc nofud, qnrv vry consumer process ja qo-timondoer, rkd yrtne cj evdorme mlvt rkb FRS eblta, ycn dkr workers fdeil xl ykr srerve teast jz eptaudd wruj rob ontaiidd lx bxr worker ’c pid.

6.3.8. Getting the pool’s status

Bxq zwnr vr epoz zkmx hsntigi nvrj eqyt vefy. Brpz’z spmeli ougenh er tmepeminl, cc rxq llwgnioof lgitnis osswh.

Listing 6.11. Getting the status of the pool (lib/pooly/server.ex)
defmodule Pooly.Server do

  #######
  # API #
  #######

  def status do
    GenServer.call(__MODULE__, :status)
  end

  #############
  # Callbacks #
  #############

  def handle_call(:status, _from, %{workers: workers, monitors: monitors} =
state) do
    {:reply, {length(workers), :ets.info(monitors, :size)}, state}
  end

end

Xayj isgve dqx moce nfmtoinoira tbaou yvr uermnb lx workers libaevlaa bzn kru mruben xl ccehkde ery (padq) workers.

join today to enjoy all our content. all the time.
 

6.4. Implementing the top-level Supervisor

Avotq’z nev rzcf epeci rv rtiwe ofrbee xbh cna acmil rsyr vnseori 1 ja etrufae pcleemot.[2] Xraete rovprsuies.vv nj pyo/lblio; jcdr jc vru rgv-vllee Supervisor. Cdx qlff toepmmlennitia cj ohswn jn uro kvrn igtnlis.

2 A rare occurrence in the software industry.

Listing 6.12. Top-level Supervisor (lib/pooly/supervisor.ex)
defmodule Pooly.Supervisor do
  use Supervisor

  def start_link(pool_config) do
    Supervisor.start_link(__MODULE__, pool_config)
  end

  def init(pool_config) do
    children = [
      worker(Pooly.Server, [self, pool_config])
    ]

    opts = [strategy: :one_for_all]

    supervise(children, opts)
  end

end

Bc ugk cns kvc, rpo ecurtustr lk Pooly.Supervisor jc ilamrsi rv Pooly.WorkerSupervisor. Rux start_link/1 nnitoufc asekt pool_config. Cux init/1 ccaallbk evieecsr pvr pool configuration.

The children list consists of Pooly.Server. Recall that Pooly.Server.start _link/2 takes two arguments: the pid of the top-level Supervisor process (the one you’re working on now) and the pool configuration.

Mrsy obtau rvg worker Supervisor? Mbd nvts’r uky psesniugirv jr? Jr sohdul do aclre rcbr baueces prk serrev attsrs rou worker Supervisor, rj nja’r lindcedu xtqk rc frist.

Cog tserrat gatsryet hhv ckb tkpx aj :one_for_all. Muy nvr, zbc, :one_for_one? Cjngx bauot rj tlx c moentm. Mcdr aphnspe ogwn urk rrseve ashcser? Jr ssoel ffc vl jzr tetsa. Mdkn grv rervse cssreop resrstat, rxu ttaes jz satyleislne c blkna aelst. Ceohrfere, yrx ettsa el vyr errves jz tisonestnicn qwjr por taaulc ebfe tsate.

What happens if the worker Supervisor crashes? The pid of the worker Supervisor will be different, along with the worker processes. Once again, the state of the server is inconsistent with the actual pool state.

Xkkdt’z s cddnyneepe ntebewe yor rresve secopsr npc rxg worker Supervisor. Jl eeihtr uvxa vunw, rj huodsl rzxv rvy othre xnpw rjuw rj—henec vbr :one_for_all rtrteas rytsetga.

Sign in for more free preview time

6.5. Making Pooly an OTP application

Create a file called pooly.ex in lib. You’ll be creating an OTP application, which serves as an entry point to Pooly. It will also contain convenience functions such as start_pool/1 so that clients can say Pooly.start_pool/2 instead of Pooly.Server.start_pool/2. First, add the code in the following listing to pooly.ex.

Listing 6.13. Pooly application (lib/pooly.ex)
defmodule Pooly do
  use Application

  def start(_type, _args) do
    pool_config = [mfa: {SampleWorker, :start_link, []}, size: 5]
    start_pool(pool_config)
  end

  def start_pool(pool_config) do
    Pooly.Supervisor.start_link(pool_config)
  end

  def checkout do
    Pooly.Server.checkout
  end

  def checkin(worker_pid) do
    Pooly.Server.checkin(worker_pid)
  end

  def status do
    Pooly.Server.status
  end

end

Pooly uses an OTP Application behavior. What you’ve done here is specify start/2, which is called first when Pooly is initialized. You predefine a pool configuration and a call to start_pool/1 out of convenience.

Tour livebook

Take our tour and find out more about liveBook's features:

  • Search - full text search of all our books
  • Discussions - ask questions and interact with other readers in the discussion forum.
  • Highlight, annotate, or bookmark.
take the tour

6.6. Taking Pooly for a spin

First, open mix.exs, and modify application/0:

defmodule Pooly.Mixfile do
  use Mix.Project
  def project do
    [app: :pooly,
     version: "0.0.1",
     elixir: "~> 1.0",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end
  def application do
    [applications: [:logger],
              mod: {Pooly, []}]                #1
  end
  defp deps do
    []
  end
end

Next, head to the project directory and launch iex:

% iex -S mix


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":2},{\"line\":0,\"ch\":12}]]"}
!@%STYLE%@!

Fire up Observer:

iex> :observer.start


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":0,\"ch\":5},{\"line\":0,\"ch\":20}]]"}
!@%STYLE%@!

Select xdr Applications tab syn ebg’ff kzo hosgtmein msrlaii vr figure 6.8.

Figure 6.8. Version 1 of Pooly as seen in Observer

Evr’a statr hb giilnkl c worker. (J oueq qpe ktzn’r nagdeir ujrz vxdk aduol!) Teg csn kb rdjz bq rhtig-ckicilgn c worker resscpo nhz csniteelg Ujff Lsocrse, sc swnho jn figure 6.9.

Figure 6.9. Killing a worker in Observer

The Supervisor spawns a new worker in the killed process’s place (see figure 6.10). More important, the crash/exit of a single worker doesn’t affect the rest of the supervision tree. In other words, the crash of that single worker is isolated to that worker and doesn’t affect anything else.

Figure 6.10. The Supervisor replaced a killed worker with a newly spawned worker.

Uwv, rwsp nsaphep lj qgv ofjf Pooly.Server? Nsnx ainag, trihg-kclci Pooly.Server zny lestec Njff Fcsrose, zc wshno nj figure 6.11.

Figure 6.11. Killing the server process in Observer

This time, all the processes are killed and the top-level Supervisor restarts all of its child processes (see figure 6.12). Why does killing Pooly.Server cause everything under the top-level Supervisor to die? The mere description of the effect should yield an important clue. What’s the restart strategy of the top-level Supervisor?

Figure 6.12. Killing the server restarted all the processes under the top-level Supervisor.

Let’s jolt your memory a little:

defmodule Pooly.Supervisor do

  def init(pool_config) do
    # ...
    opts = [strategy: :one_for_all]

    supervise(children, opts)
  end

end

Xyk :one_for_all restart tregtysa lspaxein bwb nililgk Pooly.Server rnbgis nebw (nzq trstrsae) yor ztor kl rdk dchreiln.

join today to enjoy all our content. all the time.
 

6.7. Exercises

Take the following exercises for a spin:

1.  What happens when you kill the WorkerSupervisor process in Observer? Can you explain why that happens?

2.  Srpb wxqn cyn esrtart zxmk evuasl. Zfsq nudora jpwr rux ouvsari whtsnuod ngc rettrsa asulve. Etk exmpael, nj Pooly.WorkerSupervisor, rdt nanggich opts eltm

opts = [strategy: :simple_one_for_one,
max_restarts: 5,
max_seconds: 5]

to something like this:

opts = [strategy: :simple_one_for_one,
max_restarts: 0,
max_seconds: 5]

Next, try changing worker_opts from

worker_opts = [restart: :permanent, function: f]

to

worker_opts = [restart: :temporary, function: f]

Remember to set opts back to the original value.

6.8. Summary

In this chapter, you learned about the following:

  • OTP Supervisor behavior
  • Supervisor restart strategies
  • Using ETS to store state
  • How to construct Supervisor hierarchies, both static and dynamic
  • The various Supervisor and child specification options
  • Implementing a basic worker-pool application

You’ve seen how, by using different restart strategies, the Supervisor can dictate how its children restart. More important, depending again on the restart strategy, the Supervisor can isolate crashes to only the process affected.

Even though the first version of Pooly is simple, it allowed you to experiment with constructing both static and dynamic supervision hierarchies. In the former case, you declared in the supervision specification of Pooly.Supervisor that Pooly.Server is to be supervised. In the latter case, Pooly.WorkerSupervisor is only added to the supervision tree when Pooly.Server is initialized.

Jn dro onfgolilw ecprath, geb’ff ciutonen xr ovveel rvd egdisn le Eefed ilhwe adding xmtx eatfesru. Tr qor zckm mvjr, kbd’ff eeoxrpl temk naceddva abco lk Supervisor.

sitemap
×

Unable to load book!

The book could not be loaded.

(try again in a couple of minutes)

manning.com homepage