bitcask-2.1.0/0000755000232200023220000000000013655023466013512 5ustar debalancedebalancebitcask-2.1.0/doc/0000755000232200023220000000000013655023466014257 5ustar debalancedebalancebitcask-2.1.0/doc/bitcask_silo.png0000644000232200023220000005773013655023466017447 0ustar debalancedebalancePNG  IHDRUWiCCPICC ProfilexZgTTM{'! 98%Gr 9K Q$ ("TPD#" H({1{|v}νꮞ險bO %FI{IWb@;cee Eڰ\F߉@V^pD":;;*,ngPb;'fp#$OOs2^;XvG{#s"k2""V7D';*`rHH/'ޔpdrCH.K @ ba_2AdY/٢XE9'^WVjP ; 8/@ e}`,pnb@HA'A)8jA+] !0^ 0 >y 6 Ax bx!!HT -H2l 'B((J66t^@ 0L anX= l ` G" [N~ ß%xEbAP(eerB( T%"ՋzC}FhF4/Z6@ۡ=at6]~Gϡ71N(Fcqb"1BL5 3yYĢD VuccYRl=&v;CXp"8U GN ▨dTT jS RNV6Φn^ӨX$\MfCC@kJM{5"kL|:~?K#ѩх;KAn'WB,H'yߣbx2D32\fdH(hØXxq S,r6kvBdQNC.1`Q؎%>n~%'/#o.!@ `Ч U'iOF8erTSثxD4scYO\ |E\ŇȝLRR5pVl]5guvzuJמ9p>?`rNl=Ka]r4РpQ)i]_鼪tR`K5kYPk\B[`v'Mк!QwS[ ; Yѷ^Zrgۯ{ǽgשYmw3w~݁;<$y{Ho{OL<zjޑܞM>}>"˨F>2J-|XݸxD[ÓΓ6? Og~_ACGwff}s/ٳUssm_Y;ȽXԻlv%lUպ5յN7b76KĶ017 cXo,7/7/7/7/7/7/7/7/@  ;9<-+(#AIXDšs6l).uM ;:B$+FSN([g8W7yWi_HA0C^9U HBIe`Y + ۊ[JK*jՇ4z4[9s|R^~Ai*Z:KVmFm?٭:`{]ٺzwH,]g Rz-l2q`!d"EŐcm|cf%'6&uz)e5 JgT:b}(2o . S"%e#Kg Ug jLYڝuI/]jlo핏WZ]޺_ t$\5~tWn^>S߷?qeO * }t|hkOW^I|F~Nы^^1Fozlp<-7|$67~8cI3ؗk9s_-/]Zo !P*L+Q%t-c­R4ZI]aACL`bK`d2NBwF>9DrE%KJKJKe6d_]d,BPWRkV/Ԉд֒f^3N>ogi(dDon:<% `Їa)B­##9"FwfMJ8s=!:8XzZFL#Yj92Gy'͂N̞95U~I{Ųӥg**^Yw~OLX FBef+ZW [^OQtK k*vIyߵgx__lCAG!*O^xeūQ73oN߿;缨Ƹ_3@9lXp:)@X;U{d@!lf-T!p~ T@;RsH@j @P-t`X cx%Ce uhQ7,z# 4bVdQk$. 7L%AA5IMмէm D1 20032_%*3V%M8x:=xxyGw%Hb *U ;<-hL&I/˅&RR|Ԣ|F%[5RU]_CRQsIvS:}&zjRFDcFS&3Vs !K+5kCȧH &n)m^b!>-~u@MZmpc(]XQX%Isɥi+{TsV]8ADiJ5s5/ֿjihB>a~Kzֽۇw؞c5yiBHu:z&]M6ޘ1 Gh&,S ]K9:(!v899"GxrxɼZHf % +^.%i)%)&W!xE%eMEUi5qua AM~-~m=:Bd]1=I}9CIiY+VompvQ3{ܝ\^ٸ{ y1z}/x3>;FB90!y,j%;iDäGɮ)iG2˦͹vtqłG':N]//XxFʭ:\uE3W{`ٕpǃ+CO>yƫќ I)̇/'F|#.Z.%,Wt~Y <^:$paϐ=⁔ +h?pKI1fp\߅f/8>6FGc0ϰJH q̩zoE=# e F kL|#\CV4k/[5'|@}^]ЮIR/H!d&&"!*(F%'),%-,+k%.;E.Qu5.um |[s:|d;L%CY#UM3#bY+} 1;C㲓s;W|YOS>ta~*p0ʍpQїb5'&yNK}>샹~;UW|ԮkEgSjν>{An&cW*Z:P))%-+sWɀGG|7933ef>rpmn§o}K+龗)5Kn0loLoZn6llm6vqv@N2~6& v"GO=Sɍ\?(?ypjp l(̎`?JoyR|oy*{ 0@@Q6Dh[}3 x*>Βo9 ҁ#wl?`Aa;k@lJ_0|A( 3@] 9Y=h/AF!cL?$?#$_Xc5bϜ䞈E~F^vFvϚhy"ۮB hZ0g;4Eũ9 ;O/p @/V'rXO5;O7=y_[oWQ"I:kWd--Ińd^+ pHYs   IDATx} |ř@$.^*PA  V5_6ݢ݂ZT.J+Zڍk*D%!\%$r!ys099Awfn̼9N8NPE{HjE@P"@PnD ҪE8!׉p[t†WISR~>D"dDhҌd=(e:"M;߄oVVRB $3>-2iv%3ym,˸I3f1L**FY*y&!IiFǎac_bZbipbIiv<1̸I3a7etNKwNJFuQ"dHv<f : coty1 rdx,L?7++khff>}䤧瀐477a!tӄ}oLSR^D F$юk>~*& 4hl߾}OD"'\4bĈÝO>9}ԨQ}G钝uÃdddbMDii+Ƹ0Nٽ{> 溺@ u7߿ݭΘ|b#&W;*\$*rx) &#LUd@Ca霜 8022v3<3kv3FN:$w ʎ;ߧywB۶mk!f0#6 .d?Dy(t"^ϒ*FM" "5$Iv3a$C&`<6GX]=ܴ /pgM ;p-[y 6`84`a#Vȯ  \}!]F]*r)1,"M|OF\2},2VkjjW]us'hր]~5U^I$XC\\Ulwo(Yu@2M"}85H>]WW[:؂ [<@[vUIIo6ԬFHyq{Xַ_zƸqگ\s=HU{o~X}E0! ml\T=;-԰A2%PO>7.,mܹY-A֬Y#+p#1|•M\T=8 ԤC"TDj<酅wuWy/_]*#{Yk!$V#fs=V%U.54dʃ!&kF7t+r=4a{7ۊQ2Vʵ[J U2թYB Iꫯ/9_ rwԶ@_^xޱ/hrYƷ:jUR&-P\RLoӟr:hCޒ/~A {ux%STWrBʴ/\KەPB888J7$t66qPE-[%]q^q~\VJ/)&MA_^u@eH<ŏM»N9)݊ndk'R>[ViTI 5٧#zF7&)*jV#3 "媔A͆ u:E Y吽FkVUu D Z5'f 8_DI3f_џzJ16Xr$w}:E[{Xc<*3'j 3mb$Ȓ%Kg<#b3 1tB^F vjȔb&7}5~(Tt6m7|Tf73szƉ !`ȕ>'Kwq'?׃p?o:3g⾒j *pW ۝$TjtM>ꊚU+_bu?c6oH3Eܽ)Q'Zj&෧Ô!C?,\SzHqFw^s?9HSRmF|49>,~\|*/WQT/ {W# ? i=Z M;'tɵ^+ r ~ER1UU ܹ3P;ݺYv**~@`ԨQ~zy7K.͛7l8!y+"?GT9-w0`ϭ+VwW!]"E/<|k5TW},mOOVg-jbB?Y΋rwpfTâ/TiA[7F He$22 Rҙ_]~_e]zy dr5tP*|iu*Rzڰa|{s_>|饗ʓO>)Ϙ1C/ʀ܂?:וa{"U;Խk7_O}x"PSS+ҵk׺ߢJoO<[MSRV82/ pCK/ɺu뤺=]PIKp8ޤjck?7_~5{˅^(gu";ʛq޽{\q.zĉ=j'MUH59lEE믻[|4=]?E]>~455/2h^§[-B1mi!nGAݩ{=I5`@]eqeH׶H)md^q?n|/f}·ϗӄ '?z)zcԊnDo|ݶm[ Bywd֭=N;͕SO=G-'|{oo&'\!qՋߗ& ?Tzo;ӈk{T @W& $,7!فi r}SH`$`;22Bb4a]/*0߆o(Ӎ='͇}~PJj{W|@jg5$[__/f ٤8Dm6yELޯMv͗7r {};jxoDF?~|o//T0"T2Rj"%U_ (~A@I/#v*/PR0"T2Rj"%U_ (~A@I/#v*/PR0"T2Rj"%U_ (~A@I/#v*/bH".|Ao/-x)zyt6#@BURa<@""j[h"(GEE@h %նtE@P%#M("Jm!銀"JGQE-TBFE@8T4-(@[(+# I(qZ/נQz -rJR¯dmwl0m Vկm"!Fdݢ$- ޲L233ElQ&i#zNI5T{G>zI+N;JG^.78Ssddh2䪙uTk䏳~e޷'Мdva|~ S)J} [Vʝ&`9A&L&,{MB16.ۦM &iwʲu^)w߮3?%WW'99o}ԏni]+J-PS[2V/iMeZk GG] ɂY#䴜JR#ղT̒YV!؇i3b/$+% cw&)CFd;eV&L,-e[vG/vbH#ihcco9/Qh{hjy9¾y:qu۞羐lȉ (U :N̙NA;NQy Wr_ 9+֗8D4)t*э/,wGuglEN̨S_V'),4ut*-V.鉓;N[63/?/^.vyf,t\ ٦\_X䔭-v=',)@ ;+t hjفXI?16ngowh528"U 7  3!hJ,HHnѦMHTr|OHutB:E:l6"g{Low3ƒ#Vp:@x4Z _t:[nq Y/Yay;:|b&arȿ(ӀG,qSXa]Hr6,4سdHߦwe 9d9~?.jD8/b0[ry G77A71j%<^֮['ܩ,4)A rѤRrq)\\y]r#<-;Sɭϐ& iP/dYMET&ʑWI9p"?]2iȽH~27F2z$>2-IWwsܙ>6$N~'d! {!%H;> u! nߑI9g}| .h$r5e;+:7Bh?uU7TnAȵG7Q4nL^"Ua3 YO^;|@Vn;|B$/& ?O\X ekyZVn<%JUѺe|7%Ў͇ٞٲQՋ>IBTFFbY; 8]-ke_dPXElTa2W!ydHzBDq_d>Wor& SmDX 1=z6>lʾ4{-~ojB 9ؗ@t֗t l $ѥ_Yo4y$O2Y {jVt/3CN9w˻'O}j̺vYp{pEnNKw5_\?ɕe4- YT~0q\ud؋'YfȢG˷&nZ6.J'WoЁzJfaDd٢֑2c ?/P&N$8s ;\aA+@s$0LbPo.y 2[$~.+՞ѱCKN[b,:l=[L>;jݤw+U}` ϰS@!B{޵j7Pg΁{yKpV̋{tAb#nZ2TE=\wbXS$Y75Q?ZzyQ; qpԮ5۱y;y,7),2-LT݋eNb[۞S>-ܛPE鬪jq 󏇋չ=դd tTDBhv@2OLGDGo* f4MypABB=$Qɭ1|-=yv:; ]%_:P|pT3kvihi.[&b̈IBZ>vcNMM N0āSb@}uh>" ư`~m熝pY~xCaNN;?9/0NԎܫD$_ۋɆpukێB7!媔>^l!t|ٍfBi|0WPh3}7}!u=,ɔɈ ZFX[F@1Bre厐#Gʈ ®gbr*Zdel"!m%$l]Yưh[>!Ǣ5uDw"Hb*Y!U{ӆ>NuwX :uzbFevCHFtbyC#yz5dy,I;H'ѱD>t_Du0yqX}!2}qk; :d(^W>Ly!itMC Wi뛨~H?twz ?fǕi>ݥ :Zؖ|LNCd1\$|Ɂ Ħ=`?{^›Mp>y1>h@' Si\,%Vg`,J-tP1YwYgmmC[!)@!1>z=REPR!E@(q^*@!j١(hnn]vIuu|ǥ_ Ⱦ}q ܞ%F+ O<ѕN:I(~pJ~%QtɒYUU'Fvޕ=d̘12l0>|{]k 8P:$p8.ldK裏dݮ7훠>>*ܺu{-i\u5JHGv)S>N=Twy$eH~sJ~1W8J84q>;Vƍ%\"_dr'KztjGi"\z2i$2E8ر#.۷o48 8?|>}j3vvǭ+x}>WR IDAT@ee۬wQolgEyS::T6Ux*x lyyyy&_1I&:.gR V$5om-3.-9TIKC1dA

`'C %c.zT Iݮz #sx5HF HC]p/c&EJ@CH R>^{^Ej" %U_ (^G@I#)PRp"uT>Bj" %U_ (^GkļOax'v[k%97݄@s{߻jjR}OQ>(gPRP!" ((jE3(zf(E@HTSa"T=3j"J0E@ J 5DPR%TE"xFgB ])λ^&wZqT}*wGyɤ*PR"#T}4Xj"x%UZ(>B@IG*G $<*CpjECBeoJ5Q4*6.*l|D7&pdj_r.vmھ(_:l|RSSHXv[MpE;uN+N'djDVM?:TfK˷ɨ'JI2&[Íy7ݱQR FY6xū]&^Xw&̐|HCD6k\DU inKvC+e( ?]N͖u5.${ւRkyګqJTȒgߐHv4MNm|}wsU}sN\*\6zݛsܭ~ŠF_,#w+ANS-@3{&O9د[oQ#]fUvQ9*_2۪o3ov0=\*ƞ/tpԞb{'FbtO*,zfQ0U|ٮ0۩=4}u5$*ȧA,dd2Fu͐ꭿ02)]C.dd [4պyȳvZ%"RW(CGJN 5Ic$CrGО46?V; IFVV4܎^bsC>vr%7ӽWuE86C {  4`.4%Uzf_uR=U_L#5RP_FJT_ aR#E/(eNE@J&5RP_FJT_ Kb{x'v7-:JҀ"5ݻ]+)>kE'PRI-E@HyTS~"Г($ږ"<J)?AE@IT{mmKPR%Քb"$J=()jvPPzFUOm]IG;įzlJ=:%TCQյߩJ5z?^?ښ"8J)>=E@YT{omMPR%`",J=8!i ERwOI}|4W88M2#,~;Ъǥ_ڍiw E xD7.[^'Z`{fg]+~mWT5^jm! Nq)n3wli~iЍʼ+? ]K%P);n2 ɒy2&;[ZlPIճCdEѷߴ]/~Qի#Sҥ߄].ĝtHS.'-/^މ3dEX%'Od;v1G_^rb*em}3Rʥ?}"m ='qx i 7I q' <:KnS դF\^V?z[q&o/$r"d,H>&]g}ut9YZټ|w.zi9E>v>cWW=!|)4;wܼE:o?ws $ͻm#NyA KvVW꼵3NLqJaSN>=[w Qgv8U{'P<-I/+ukvwHK&uŬ@t!v[yd#tLviӵfog@T`H@$[̓Lu ΪvۅtTHS)/d&^&6x$;^SFO=@f?,'T#75 ]T}]h#erHNTԍ<P4Gq ̔CϔBr`."ĭצbzz`#m6o:cۏ2Mݹkl9~^[e pT?dwp8I9c8*T J8@0(ɲgO@8y2m_<ǥ4 V%F/Y% Í9OVp `@*֮o/ӕT}9ljH-_2HdTc4]1I~aR~rH[á݃>.]Tɠpuxb{0ih?YbUQ6t9-7[=)YF={pԞ^ͻ4{$=D%_$o/Zy l<+^GA}UKAhkU֢{[|WXS/3~TbҔ_o֬i.5Uo>x}Yy?s{nӳ[[_>oS![l+~P8&ަk{v]mYx8 Ԟj :EIH Hddxe^|!U]r{0v u$-#l"PW"9h4J]md y:UL'Ԁh$CrGО46?V; IFVV4܎^bsC>vr%7ӽpZi 7 ߌ9iD0*qPqRT=Ɦ&(A@I?c*PR "T3Vj"%U (A@I?c*%1tHMTz^af{m[~ZINOxw A@Sd " T1j"J)2 E@JBPR%H"x%UoZ()j vCP7APA@I5ER(@@IV(@ "Po qP+E EPRMn(7PR8""(@j7E(zc E@HTSd {i7N):sJ=5"`Of;zX]-yfA;,JRu=d;h5T{D^+1B@I8͎jFh@l^uTSoXmݺ]كp^q~ImϽV(Dꃚy晃_9 ڪi M ip0+Ɇ ַowؑ9rHD)G]d̘1މڪ!F~Hו*P}b֬b0raJg?Y:E[ͧ 53ij F8$ .Y#<=ezp888 gfqb?Q'LH?H @@y/1ydD)]G`ڵ2e)@AH$*IuoP/pU`V\=PM˗#N/|/38̜լVY''j=G⧤J ժYrʯ4*Ẉ%/ʸzΥ^*w_ԩS.Xf{^{M@"?9%(DJ5LN VHSR%@ʃ+n\ yHm1KR"ك p9[nI7n{O}(ЏdyOvN\'VAHF 7I&Q)?X^*֦SRm I}V%`ӬN}CjV{:F{k3uX/PNQ^z%y#󟹩PMUێ@̪Ґ)}C0 y&Գ?mNI5Ef;+V#$F\ "}1+VCIs3IDATعS{*OѦ˹2/r93_PQw4PIkׯ+VW^mƇG#}uS $붅m.~}+l c#;;ƒɯ~roҐgHt$@CoC&nv= "-dz\jP?h\ #I֤H3ao|~MM^Q3čo f}}23փ0]Q[\e0Ռp$:AM K>IZ'ۤe!6DK3aCFBuIoڳm0vDvjhf!\ iK 2݄ Y8lBe](6B6q[M>]byv%YKǐ˙1q㓼ǸMd6!D$q4o1M}S6wM)$UX~"3F?L$p"Y&x2] $h!d>tDݸIgqIK$Q;& A4;nH$1R3$=1n t;]67"ص}SR,RXkȈ>Mz6yLtoi6&dlo gg#Gҡ3EϤ&)$f614;nncדm26w6rJ фތ@k6gIcM;1NB4y 3ߤO 4dɔi9 $.7a7i&Lq֞<#v=eMݵW {:T;*F,eIjL]2?;'&D">}.8}%<lbb: ˤ1LgҌO£oO AFv[&&ݣ=+)v+@ `mb훰MM3u}Ӟߤ'9n&8C,cm$8ChN2Iהl7ݖkgOHh2 MS:@5dF`G)gE&&Աr$D׊`aMz&l|oF?g;=?ʆT+I,s q%!D꘰0L~{IJ5i 1Ngh$0:㛰'%8( 队,l4|2][(#^wJ^!$hC|ڋ'uѡOgډƢ4XÆXƄI(DP:WR,Rt#IVݐ MM&lnbdq[71l$Mivt3ڕT[c)qG 5vلf≾)nmBM7anZda%dh"3,d䚌[[I$(vf+"icWʨ"(@bW B3j IENDB`bitcask-2.1.0/doc/merge_silo.png0000644000232200023220000023135313655023466017121 0ustar debalancedebalancePNG  IHDRP!1QiCCPICC ProfilexYgT˲ّ69s9`TD" (xP"ID  *JF zuYkzu 0("HJNr@8gx9_q0G%tb3;f{x{.]ϐ[0}(:"ƨ0Ƈx+F~fH  Q> /R 0#zˈn {Dǿt¿ O  l"렇[0G/|ϟ!|@lAv0yXZ5} ma C"t0 kq~z09\=F4=5Lou1?G(h>o5/o ar09ف XE`7ap$9aN =p`~pQ~0=p/iҿ@\j^c1w^pHl/ OIqq0g6bz-Y5>8y$/h4< upA @G(+|@~ϲ@m`L.(?~p hp$.ˠ p N S`,`l]bX!nHd!eH2![r| (:ҡl*VꇆW}vHD"qE!rMD1C">#֑IdB %H= C#Ӑrd هE"( E@Iqjr@yBQ T! ՍE͡VP?84Z 6A;}t. ݈A #Qc\001L=!fbYbX ` 7#%7,!+YYY.Y }dV^mw)((4()Q$RSQPLS|T$Q&PSަ|L9GMEO%JGu*uTp8A6ufp[ Ԓ&^ԧGiihthܡyNDKN+HGK-m]c ˠH7Oǀdccd8Pð&ct- ~Qё1q $d4δɬ͜\<¼΢͒R2ƲJ`5`gbmf}͆beaföĎgWcdOco`@prrxʱiYŹĥu+>"7&7;'#A@'tVx8xy"yxyvyxxxy_Q)=[?_?)@.,''')($$xVYPPд0NXK8T\FDY_DdH! 'Z$\ !(F+GOHPIHDIJI2IK&I6K.KKJeIIV1Ii&+*)[$B'g(wJEnU^L[K ܕ&ʏU**TUUU#TTIը}TRVTA(Ә$hk^՜"jkҮ#sLβnnIH}#4AzBC^C_Z#FfY&&&&+J'Mͨ ޚY ,L-.YL[ XY6[+KVC`lml؞cs۰׵ϴrvtxHxرqI)iY ɥZ~C ?"t$HQG;h܈nwN5?Vr⺇GNJgg/mEo l>>>}5|/.i-HcJm[_p $ tl   I U fV oÛç‘g"4<Mgw8G'xN$;s,tTϒN;nKLNH~wLm uJXYPHSR Ry=INM|. f*f^tq{FwRuu GMf-6vCoIԕ3է#oGw[И5645϶ >jSkk'yz;O{QcG}=Xpӷ#GS]]/m{zv=x_Ł O)|]JBxYxWOWWVVe|g~}M~ѺFf֍m??D~!pu8ps!()~I @8q 2%ZF` dȳ))#h&Yػ8\ey^>G3^ /lAR%F}9lw1h$bGgm_?qeAMEFducb㢏(>Yuj"Si( KYsfiүfs>tvg9<|炘ⱒRU2r JkVU͸Y|V݃'Gjr㷦6=vcZ^L~e׭#Kۻ7߶ÓuO͞~zvCCM#Q[/NNM<cRbrctk7Yss^}5/60CGҢ'Ϩ3KM_җ|_a_edf6rJBP#" k*/F K"GP)ũqJ1'O ѱp$ss-8xxE1.xA(C8Eh^jWzA]b fJ+*C j5l4e贾k^;``oa$`Lcc2o:hj^aq2VNĞÁȡGr^F,(+$1|`ZPjِaI  QѧbN=s"dL|ܩ Ig.K.;s+Oi;.e_"fG\:}937#/-? |QVqnIɕme_m_Gߠfᯕ|Kμ_> w477mul3ۮ!qCï㏚}{Tz1} <{2H98P0qDuit˱񂉸T'Y'^N.I~=46]|B]_/nf[2àݼ_t!(A Ph4 5#"QPq4%t .TL,XVI6+vD,bBO:o APQ^y*1- ) 52$YAJyˆbҞrJl>[~cҁ4Aςodل+DFtWtUX8'N|<9}5.")gSϺK5JSM_%i.>NAEEcB3L{#]=AX̤4|5=c3Kk#󅷎/_SC0HhX$!&Pyw[EC% eIUQ5Au~ ^M^-^m>]!=}qCe#]c WSYyE'sX; HzEg~7עC/0ur=D   "xI GWN;ß}'2L߳?)XBWRv̵/kucJv7MlcP}@jXӂ;C=#C/FƟ7Y5>0kV|jaEϘ4_#W[Xx9a{uggw؇~ כ`@ T`'YCǠd E5(F32ُ¢LPWhAt,z>!lv̂\B,U$WKmNJSDkLMWO0`4bB10ذ~bkg qprCo =<ռ]\PUHL_ -#.(!)$'m-&,(Pة4ʮy_kYG^~q]_ lJlg ^N.:;|4 ;gOt8lr/L"<#Vz'=N-$$O>i3_ɔ؛}ҷ˩y~Пs pHYs   IDATx} \UMSRK3l,тi_[)S_?dL8i5YBiԔejK* &({rAT{ρ=9yB"(".M|ZsE@PE@  C 'L$ZU)^P {O:rW?^3ikm*\aQ"^Uv󳧩g#5 ZS€E' Ҹ]M̵ mG5 s&a/5ӸgU&{:{\6񠍇PQsC6d*k~tk64j8vckC&mn7ӛ89רKgm^:cIglN j-BPs'ݬI&4o޼P ^KPS^;qBB:m^~sHaa/ǎ+\~Gⴋv}͵%JY(xij_\!HjOA@xcf 2{LG?:=$9>h7kٲe-Zo&E p<;$mn/< y~gx <$;mVymx]0ncK2!f_{^+ en̋mq3$BڒEn߮]bw `X /}|i6mo߾ j͵fhQӦM!/?mw䥇ܧN8a-ѣGqqH4B%K[C8||P*9*">,EYeKss6>i͵8pKQuPv ocvm4vyN6BuYVUi$^󠠠`m۶)I:}_x;@"#:W7]uv:WW:sŴe',G'a8 ~p`+iKo\y!#m~Mg­ko a@^L46ݸMG: KکSsZju;˃k//r4t1@B?y!6$>$E|Ѱ9r"i<i<}>o߾c( d4]Mr}IlLi*(P76B U#f:w&2~v{g u )yt,Sẅ́Rଳ΂te yH#`o%W_`4޽{;vݵkqq*:q ~aGqub6`APT{f0-'wker:6УG@{;w`xmlԸGy{-wdΝDz}8.=20"<Ѱv% _3@8[7 BΑ{n;/siٽ{w 8J⨩Y{ۏ㏇lrL'mrL!jHƀ6fch>4l{F) r/N1yu6Ua@_~vfC`àCTW–إUW]rذaE.!$O>9,~Jf6#X D묑Hj8y{fc;]!ә7ɔ砋/u]wQ[T}S4T?Α|Z/$*2<|hjՊ0MCm6^"G+C=nvhѿ5":1(y$+so+>D6 [Q '3B]"ח-S9͈#t'y^W@e e}ES".!- j睶0"!Pͽz ,9[nٜEt NDvP^ zG34Z%_5>Q0<A=2?8**_$ZO0jNիW犠\*)m0`4{<B, 8;~ DL4ețn]wFEv`Txlike1Rj!.1Kr>"yqlB~Aϖ55:}zW8bZ@BDjIy} tZB) (Lcm$bE" ]/衇:>o5@")9/Yd̷m;P(7h;y @tL|:wksM(曇etʮwF( 55l620$b rE g}vDS@!hTiH("`gnrLCI&ueM4ѣF` D(x]JaBPPSwRa(L1Z˜1fԩSRs "={v֊+OIB 郚67Y 21<-r_)Szq^/"y-NORl)0 ~ZS0 BUdll &` 6mZ|J([ou'.DžΐQKH86W[-Qm pղk*Gl㭥?\A pe&it-?bxQ 9 8T)%{M5DHҕ~z` h|\MJ`%%wR"lݺrlgG(*I_SA 0mr&hg# ; R!F!_!xܡE8~8+˶J=(X2Mj=yDDkݨxqI~g a sz Bބ/\L#~wը %C `2J&Ǐ|rπm||شi8r&ۮ47m5yL+ w}7H=/_n⨭x$˧1c <0`r߯30` (֔g"wF,>c<Zm۶ji< <曭)&/%'සƌVS]Rkw9sti# O ƍT^ Νk q7}'OXMWj>+`ժUHMMĢ0J6M P!!!*uY+峱Vàƀ85@M,g>(|rwE!;x[$ǾZ8p ^>}zo/Cב 9 \ꫯ}l]Z셤Bb(bJڵ+/\p5kOnE;aKpZLTlg(PG!<ą]\J-)}ԚQ8pk׮nݺY_YrhԨQvȓj€+-[F!gk/%62Gs=ך[$ݽ{w p9s$U\vލ{Z723jn˭Lp@d weQ{Ǝgj߾}###q^6{]38p%####q3׎qz5X6O6' /<)0've5>vJ]t3  O Jf:l<5\BڿE4xs~AVW [O8-Jnh:t.NsXb9>ys\f dxMLOKәN8Y#4/5簫3äh^2L|xM݌G sy&@b6#uC(!3# *讎ߑѐ>=hsו3= 0I0PU_#6;mӹ?^Ә×hxMitgR.M(\4TfN9cy8!ϫg:~^T\reMR(/  Iw!bw=/-"("P4y'n TSoqKE@h\(s<ǥ}Ll٢U pF//G@y<=.`W:'h^ (@B@y<=.kq"("a0aD("7* 7z?E@PEPaGPE@oTo~"(!€=-"("Px0P*(ADELEzљVc1{eیJ >r˓I,LV9c@7 >((AVF:2 3"4̹Iوg9TWILR ̈  %;pE-Cܶ LkiT# /[j |v$:t BN]K$?ffm(@u~/q !t9h] '\Eq?ݭm|]o 1-_;( y5px0P£,A/uEbH<i7pYp߶ 3FMdi3q)r]ók-8^OsKhHO8fdE@MJe v 7ٙKB1S1i1iF8^rI/G܂.}q c ,@JhGᣑkm_ӋhJw[>7;s" Rơ#Ic5C{)HA>gb(x0Pu5f%tŲueC%`֧l0qQ IDATa F$ơߴ|6k=$$Xhǯcy,ICV%)2ON]Ó(8L`ϫ`Ŝ2c鎝%`ra$+0e:y+qQ&L|V@>w9f-[< tL6V`0E!;1E)ObD HLޣq^x0'%Ha g4DMۋ֥4D&/9֍DJc\Sg;̛U}ppxk+ͮa݉}[2Fz iqQD΂ԂvjѢ}/DF{Gm$9="SQK* -_ci7cpP8o^>CFQ=z0G]íhP+^qIs4.,E@)#<2 c`npl |n7x J Q(l_)l09 ˒b ‚&͑nX,&HE{*cYko$Ὥ{ٶVC@ G>! 1#Y|Җ/Dazw.ƃe#p5'9o863TLFfQ1p^ljEpy4bQ/nj0j<%;oýtA/擆?@nGxCI0❱cl !@dR\r܆[p8b7X{`rD1q6^\ҝT"I v'Ly~—mb 958t8ϚF(éxmhLoIQq`103m{\4 3PbDHǽ 2l͍?|o8$G;Qg7ۦ`%ݫ =#fgv8N̓0P`3M@m8ǴxDttQ3dX.*1#D8]b$t@ i!͝](4ocJ1#E?C{Т}Ou  whY͝Co &]X!7+BGR9PY9c(WF=vry+L/ACAČ٫5z @ڴxB$~ ;BiƘk {1+*C >DKiOOB~F=|ॕyrՀ0:azTB3—筘qC.t chXx I -t^z}aRfҤI;nT*?@iN~5#xV0?RݯQ%Ȫg~.n#i#ɕ j"ZmAmAP* 9RP]yilnvqʼ0'4'qaaiE.hY(Y,!>b.p C>E@O>}񼜜 <.,,V׺ukky˗/WW{0P0ƛ={?#++mu;vK.ݵkWjժJ:qJJJ,_-! 6  0W[PzE8y޽{N x(ʮN:Yl4"(7yr 5}{.*Q0|Tnj<E `Ĉ۷/&Mf UnIl6ݔh97/W^YfE@P~ߢ_~ݻ5(8|ݥt+>< /7tm]h0a駟Ƨ~~HnJ;wFpp0ZhQ^ʉu_* ̏ļ)^zeK"(uyx- ݾ}{km^˖-D;#O#3f8-yݜ~4~TݫQ40ZPj'Ti"]Q5 㯣{#W"4> X>Vm"PW#&} t̀ xu*"( ԵΊ"( l`SPE@ETŧuVE@Plx0Pk%^"[PٳS*OH 9(\E;2{62¼󰵤(@ PIUܼ@x|de%޹5k+g#2& Yֽ2k<^Y[ɋ20?> 8͞G}NdT(s%F"=[qU7y{0Pt0 3PhGؿ+_˨g?x[*~ȪcY-ϣeU9 6㎾],4%t۠EE@Qvbwx@ǽSGK 1Ž8⾩U ~HNB=~ۥ;[s7W6=-(qnrm}o4V5\ny?š6_8 UShaAF9ZhJGld8Å㑞9Mf/%L(>)8`Q><fh/4Y/4~V;mԩ(>Tcb15&RxD$v&tvbS:̤xN%?enq&7iP $V"S.)֮eygo2\7|5cCfQVoVآ1 ͘9# 3*0}q?AG2ibMcj6u_:t*Lz KD-ldž͘4nؽ0 -}-1Wo>"<(c+1$`S8JAλM'9/PF.A@1\46}N1N yh-("/A$&S_ 6j>&M>z !ӳ`& 4&y"WȺ[V旎UT>H1uS{t;58X8];/:g[Qܬbh)'r½鱚OD:4{I爻}ώHСݼZ:\Ks+$>nov }igӖe7E[ Oc`&ĎؘķC.y󷹛v@LBbi93jE@(C 852oqFsbD,£ѹXɊdf%i8];$ [L?zM3 -X^i#UCN-]]9[e}*ŭb[ށmhAŘzf;Pw}-9=V)ˇ/R ϯ!Ѯi*aᑸUwa֖_B55GPԳ}kX2&Ju;@°PĿ!n{(:V]ř1p&BE@(x;i ۂŧYK0wvtyHX26-:EG>Y9j_5F{D/pսe#*QšPG^'!ZIs"bߒiJƋXkS. woMYP?oQdU@WvqJe2 yh1r()1]F<2KNJ\?NlMj0LۜqCƗp&v>"ss.$AKz ]"4W^nذAԮ4iR-.9p4GT6 )-v |WO8,)qX(~{:3).tޣ4'GV)X KMãœ*Ep*V QBHZ-83"8LB><9oɛV1m=nxsߺWa鲱(ɣRU,u/|"KtK ot/[M$SgS|*]nT>tT[{1FPg!ʠR23?B:th chU 8=,Pd?Щ4 DH3TL<{܁!VGE@p9Nhx_W̒'CO&cA§*wxKŲyRAβ}-,?;fKZ #E3{Pe;8Z 0U`„ sfy;vO>y`iӰe\^?))) .z!Iy"Bg4U#՜!۶mŋ߶rk0M>h kޭXHru&DFF"L-!F:)`{T;5w@>Ůd$nxr?>q(v%#qYBfJZ ȸ[Vb~z]6UR{ɓ'x.nyjX>*.IH` ҩR$خ kqoD^{ln av+ hm(9ViBũ.عs'֯_w?h֬Y]ܪbܸq!C޺V€ABm@ |f̘qhϞ=b_(ӗΕ횁fÑf 8t laj.鿄2!XVUD-s%d~+.HHc^FHM-#ku6l@\\{v˽v8x '€Z@s=(K#e#Յ$rM:tx҈A5&qG\ W8vP!5@QQAGsZnݐ)GkׅsJv+rg 1{vJE?d$GRf 3UXX)g#.̛:[O,5DPà5?iBjjx-Osw61OϞ=E::؍W E03. ck'qoKYr,KQy7F\qG߮pk^V!cT*@ OB]Esr;.hy0~x}0 áŃ\G@tXSzN6Fq.qp>_T_ةwjpesw/.X4KÓϦ-Cs74@#o 55c@6Z~ ,؄qSp%O[vgIHL2-Y;t51ԭ(w!pZE+C%9^F ?w…j ]"b0e0Hݯx~ᚁk{D,JXx$n+ah~fm.#:m ? b~2ۏ6> B/ޒnXwgc%r|h &t3i\v K,>^_$"bEwԔT";z:]Ss?O\ 19[f烒buPHaR<3~HŠ42 }o{e BBOP$ܫDc!&K2P" GAA`%cY\ǓQP. m\$QE#8q_|  MzdkPJ)_\aB!U.`XB&?(1 )8 *W{s*kQE.8t50|p:vEuyK;!!!XVhE@hp|!40@IFPEH711x w^ Mi0aWz*"Аȷq _z%,ߛGϜ9͛Z%?0piJE@P<z7:u¤Iys<iE@85/^l}L1L?.ߧIѣGCU3VIP|cǎYjmj1!nF@Mf OF *@B ,Y'|ҪWV㏫ pO r{c?i*XL3@UW~,j[ +>"P ɓ'[*GTj"KNL6M>P_i0aoٲE;>W?DJJ₋^*jc}<^`)a5'~ e 1hРFcU!66:rԜE1 mC`ԩV.P^dj+)"TDk=+i#0gk}=<CBS:(@Ce˖aĈhٲ%5{[ZY9{_nXP9@wߍM6EVmU݇ΩKZ /}uѠQJv+QdAQ|m+=$Hʬcyl.(UX)rgƑ]7uT@E@i~g\|z)(16Kk#;ëiB;XZFE?{IRCVRmy=n.QmuwGGdᥙ/`2FU"xw>!g̘a-h=4_~'rmI~~rhDs/H"GchJGld8Å㑞9Mf/%L(>)X8`Q><fh/4Y/4~V;mԩ( {.b֬YA&5.{,_?U(y+1W` j]4%lu-Jaa&sfI#UFB(D8kU5P'|7nĬ$$oj 3g!bsGn쌴ظ7I8bL xO^Aؑ%aE1j ,/EiH6U[`S[I8e՝PUMܹOQߕ ׮]}k ?} Deu;6lƤq׃{F!Bmkq3:g'#bH+1`Va  ~p׌(cݲ h%YG3 D"zT".ǓXVkH@~D`E@(,_dgg[6l=\_E>,Ə_0 !Xa>Iwwtaqv{vD‡U}]zIKSuP'rYm1M[oiGސ#)?vh X 76"9-KPv<;bLK!` eQԡ(>>> ӧO#}UM.|eQx0%"Szގ܍9GbJGֱ2a֖?~CК#nz '/ĭ~ GHj$ޒnXwgc%r|h &t3i\v K,>^_$"bEЛ5"&`ʕe0aH_~e~P<S(GP_<ev>(!V':Tq CHR_>>~X V60P 8(@uO_m;j<GZ[7yPajl4DPJp'ӆ!ֱ'NE]T)zx#IM40Gw}W} },jE3ŋ#..n6,B"j֬9h0a`ȑxWкu듗҇b؇Vk?pù}zM}xw]Ի믿$5"x*|MW'U\f`LL t GElj0a"( /{9iӦLPAJy߿|< >hE-[ɓ'/@hh(Hjj 5Jc*@E \[D)A56Mkĵy j"()>G!ކ?^xA5:T3piE@nu-EBÄ,VHu4Ez?xƀRVE}@@4Fh.(PPl߾wq~4a ӦMÖ-[sypYJJ₋^*g<-g;L@XXHjN4>>^^O@̧94#@ 3<fh/4Y/4~V;mԩ( ͔t!δψ\ĩQΝ;{o%<+ ~VEP]7ӝ}>Q뢑/aӯkPC F'0{4Kxto>> UiC { lܸ’&̜tH+܈{t{#bnI_"VR濌a^8# @S%AWg3غ.@ygE"<(c+1$`Va Xq׌(|λM'9/4 D"*E< 1e<1IwTw6Rq :TWK tu.0v$#٧ѭMn:#Wi\:2 PHMMF؁-Qc`&ĎؘķC.y󷹛v@LBbi93,I"P z Q 0EE`i 9,4qDw]Wc.1|"e@54GXLIH:VÂ6>2\G/t(\sZ3~Mľ5C [!hN#0,:[7= .`8"DO Dn&-w ndŧYK0wvtD_WtzV )u(!2 Q,ARS84ᵟOo,lJFՉC)baR<3~HŠ4Q?Y !4 ů8@ʽJ[P[,YD'tAA`I]b9.sx<‡rQo0V.)0%#o&[VXdffÒ%.+ XGHUԘafAqy 51A!eˑM@3w  `9EpgVgpr*KƄ>c MfDJ["`G' >CEvTjWEIP< ezL s3㷿5p" M{gxTTW_ C@VsV'"4'^*8%*()q$Jj!=q9a5Fh,N:kRGG!m6AEԢx\0x /+wWzsR+0B, !` x͚5SA Jh<#u%FX?R=#{n<zON |kSC@gIΙ2VƮ9s ::ZWSf! ƇMOjov uCֈ;6SSf~ֻ)^->B@wUhǢ7,[ Ǐ"z7€㧻TT:|z_EwGzK-uBE/SC󘒣?|j8(믿Y3F4" 斖 ?6OmE?pܫm ,; B~j1u;/5EV^Ç#**sլNi[ @Љ#Go6dff*JΥsAi؊D6)R^,4_U gd 艴gk%%%شiz)]4X:ڽ{7;ƃNo\F2]NJ{w[Qj΃eRb!WjRI β\-DX)k$0;vlqxDŽuVUdwC z/bk׵ ;*; R*kwnx5$f)gfΜ911rP͉[nG)5T[,ԡ{,C<$2#,gDw}%tɉ'VpvI-m]ڸju:'T P_L(ORЌU)L+Gg\"m}%nUVV?*É_~ce,eҾIj]cha+f Re]Pj`gy|&u;ų̱k9n1LfF12FD4FPh*vi>}[nPNjX,q``h̚ IDAT{,=?eW4Oѽ:20JS~rc{W߿ ChO>b[^^ePХ}rlOd ;+(8u!j: Hy>K/A+R]$B.C5[w޹7%% )4Dri2qse\;p1X"v >,t 5" C" |HCjr (' Jir@ډ譛6mKr2ycxT9J%/@F4x8kh0__AҘw_)' 5@2({ knC| 16'􊏏'PZ`O:c\;iQP їbc?hvL RyFP͌aȘԅ'<,4Oh}bիWW^yŷ@֘9s, r2Lh"[IE:PL- &! V Hf@:-sRQKSssh2S#..^20pb0 L7ظQ^r|Dִ3!p)DB1rh u`.}+-Ma R]Rk4fGwBllA>#P9YNW \xnY&L(%-tj=L~扤_$2Cc"H@$U@g)ޮ]<ztܙ`Ch Ț3l -3Lj(-# rVYtdzS4Eg֋<3 Z5W'OŽd  6 'BdAQ d,X@1{ji=y"F@Ѽ:Rq3kf9r,B1iDSжm=z馛O?tV51c&/Ò倊_~HV1,jiSG,v+1YBWa]q9F=ml֏;E$HIF S̀Aa L$Mv7zv=sXXZd >#|G x6m믿.'۝'cO'4N)N1"I>W/3`Zwat,B ѝ:/7nEir 4;w}\o߾vjA_L!B:C31LKq KZ(aS~E92gpGf GyB)4LhBO1z7։[O cZy1>>>azr9L'S,o-mlw,کg5a+;yNiaaaib#4Of4O3e*&0 h1ͣ0\:ff@d:Lȥѫ:.Z` «!(DiLN:Bld|MxY==%JC55"f6wbVnxQTTd%Oq6E`e# ҈JC\u 2S^o]}) 4o^E]:E0yɄHh*=ch{z'DISn h>>_C(%Ʉ0Сw./x9p}^,m~ͼp88˾&wB8ٱU ͣtF\ҿlֆl"''Uz'4Pm)CO7J[M3Pq]@)PtiƮ!{(S/G:Kٸ R_IQpo׮5Hh8H, 5E)".x\o¤1?1ec4T]KGQpIx1$L"gQOayN= 01pKzz'4JO[ei+FC<13/B$ݏ i#7wJ5 Łos{:)ݬD119,$H&_BD+'vE3_<寏CQNLp̒~SG5ƨ8gՈaKCW \av}!v2U4Hˁߟy(zŽ n a̢y{L&^ӭuDO̴};^pt9/a/KyEK'7Ni@&Zܮ4t  L*j*JC @}č2 {Tmz0V15r@Q}h:%0pIY'4Iv]蛸ՠ/JM|}S~Q0eWq[JO'd?GZ@F!%9MZFZgLAy2iz'tOhعNK}|>8Dz蝞q#gve"{~rNrJx {. 蒞N)&~^_Si[FFy MAIP JU Xuq+ΡIPnNTZũ:ŮۥN[c񡘍חyǞduK'1BfjHf///)Z";F.1vu]4\]r4dW&lz;M:ٮ\pNYJ6|_BnWDQ9E֨xъ]8Uƪ|}nIxF&FJ-R4nہp20P/^S(n1r sϕbwb*&U'>7F- C4ʇ4Jh'ΉHL@'t$N螘Be?GWtOCq+:LWz'nWw]+StNLa&N4}h=q0h? ͫ儗:E+荘J+hg露+K܊V*Swf`\љژiuiFF,]\M}gpsSeWʣ%%qK)^yRI[;0w6~<ħzpq$7Z- m:i\Eqh\m,m62bsvia5p+‰[5N1\5\e*?K8%n=\ŭσk`x;ܿDcM܆20p`QtNߋ)E+StL0WLOsS%)?Oq+?Z5A:WGOE]/nWSr+lXQI#bWKщ[q+?%.Jsӧ*}q%㿲@mPRS:FڈxHR4D(h+2{ U<_WvVSMG1UzbR~5+XCM¥2fS`zSOOTb镸Sf}07ʮUnwSy)Z`ҍ ,fIT@FjpƭʮWp &j4T #~i]IoDFtm|V01UCUv/J:ĩoz?]w͋ LiZvDŲåLg B%20lpav)z%r-J蕢a;SᔟkN«bW߫82G0LaiN.B)ZLICOG4>oNI\zzjk:-q]2u_t-p͞9/i)F :2UXeë8ԇDTz+`aniDJw}ZF/UVv1%EM#aZ`*.tiN`^LCh)1%EOĮ3.F7E[1*ޔ4['newKQ_H"c}x:kġޮ`3Їq)"aOv)2_Oĭ`[WVa*3K\j(&*]`%4HQSv1gWKCUJSLRq Loׇ9{m:EM}^)p. ª~Fiko_2Wl;-Dceϰh 8Gժ (zR2UVLzɋ 'ϥ:ZU)J5cZBŤ\zZT&ʮՇu #yQJiʬl}&d?a$kF,JWL}8eW /]ŭpkAJmVU\zOW:%3J]tyE{[iD5L'\h]E[O~ʮh*-avt-Jt3Me =ޭ1)aŮ*>Wne \ou$':^*C^a$jT0Î^_ L7n b?V /Sp֛7~{f&jp;+ׁ)eכ.xQL\ʿ>*+hzzT7~pzSU^ٕߡL [ǯ=pCTf1# WoWQ)[`z»]zCͯ54x,49,ZP ttϕ.-mK3shazHpL]ko tOxS8:!fmHNFBu1:^L`20p ? d,wPZ\tm11 -Cd\' 0@"%=bn|ncرE3gONN.}RRR!oѪ*M֭9oC33z՗`cСC'|#F+/qb.y_ѣG[ Uزe :v=3{ynɒ%ڳ:u^lO̙cQWaX;D<zLdLduRyMP^,3ݘ8,hW /R1tɸG9#IcI]}d-q$O4'5 o&y_in#l/qjt7r>:֭<~,Jڄ(=!̧g!km۶Rcb(TG%Fؓ3pUU $P^|>C薤57FT_c㊃XS*FkOR >$^kkG={1Z1Zzk+ƱQÀ,`nmGƋ%Jd1,n֥[g-:.Y6ljMPLgFmkeao->,7FE7j `hg!ǎDqQs TV ee &;6jrGۘ(x{{u96m߅^]%<"+1@vv.ps76<=`='7@xzXPT'-fډ8[˰}FO@EEEx9Lq FZ{vjy~df;7yzMH͎>XQ~nZ"+amPQԌLxA|(f9(+)ʊ6[+Y Êvዄr{ʗaCaI9앥GlBG][QPR"{5q UkN`8j28qߎ;`uAq(-. QͺGXD.ĨA*|TVT?|ե :lvE[I!~mx"!KVGI~.|";.9coR8OI㴸T7>řw#FM/#wr۶UE67]6[y/ IDAT"q%$'wF!gd+>v '2H,3t8kw_RlZgނtز_>㷡hJ<\zd8,dJw>J<,m+鏛oC޵x_p÷wd*K0q W ϧNvlx;^儛vhx1 K5)p"nx;8Q:7!,߿4+/M{d-g_A~܄vyg+ eUa`ZUuڅ ǀ~aFZ l/ &7 97#9d Ƭc6يжS_\8n<ΐѫV+J~P };Dg32*ggh<w`3.Avf.CpbExNaBj+a'o_v#\.ƀ@oGFA#Qo\OwlЎ,UTB;bN,?ĻWCl$"C%;o:[ڄxgXv>Gm{)/"5j;AmXoH7ÇO_! PIilJXV[U9 )Y'<=x,w>cǢO''NSwa-셇oӦ}y?-n1x¬t$^p#.8L} a[N|iiIiTCϼW}=c/}C8NP2r/BdD91f.Q oJ0 |ر>/H4vȸDƷGp_4]h׾zOѻ'**lQҋӶc%%6w.GG&,_2nS@՗CX.b'Lfo}g̘> і"Œ||?7d>./Rk8r1TT`ᣏ?.EuХUc=SW&<%k6p@uLn}!9'TQk', VĘVs!}#lܶ6UMVQR?7.Eaa|;g Edv;‚1xhoDǶؽu*aEpW។d''\&?Wf,%m"/w m,QP~ >~1DC\Botٳp(o9lɴ0 @ #; Ày!cnUZqu-W4޼bT'r3됐wftЎ3h'`8Pnvڑ3l" xyDAB`\27ᗹs0 ;ӏœ W,s=}83$]h 5r2H ؒ _p"|-kk@ey< czJ>k\sx?aPrOd Eu8bP Ϛ{~Q9 yMwuk "93GdO<}؉L[ c#w; ꋸ4TT^D:{X# $w}/h?OOTGq,**Ė{QF{ۡ;m~T]^?֤'&ه?=$NKzu2Fu+oOL_$n?*#"q py *l@-LF.Na  e(:`νܳ;: m }ӟٟ 1%f-g2SdzO> kY y%D `} \EKWQOg9m؎R&<m{Duj2 20ElqV߁{ӳvs 7z/$ I);oac;Gcb4Tqّqy?Ђw'P_$vf"x;N1hԙ{+s&ѵ@xHHba{n2Ǵǀ1axyxT^O!^19BWc =p <{FT'JۗS? gbF7N\4F*'C׹;=z?nzU{cE'[i&"? "ƞ'^EB|CG@zQ2_};QoTWU ;+ 5˦l67DƴE8wq>g^p^n{37F?s().JFK˰rBhĀ/#P2Pyz2EFFr^  sqY(,@xd,"B| ϣDêFVRD woذ]t&(F1_og!3\~ۭ'D0ثʐ а0J8(5Iô+ gY.S!mqc%DFrW:O!dk mT.]PЙswoBvZ`_Ts];y"a(?8euٗ, b=K swV<ڭ{yD$&5MCL{m\y `h(/)nWZeND`?Jefs/J$qR՞3f8Ǎg: 22S  Hv'ķkGdqxéҍ%ǘi>}>gWw!O 442y`@R9S rȨ~@7߉ RQڵ„=z#>Mm*̡L3EysBl4MH>"Sʿ)&=xA矧码R>>ki>ѝy/E7-|cBu?J.?0e&|n3Њ+M#M ^oj4lxFLJ ̀ ̋b0W'5=jd`@ڬ!n%i'tfS1? (-NYUYݲ3iǀ Uh@ q*{lU|UGXJ0eH.h4Λ}cR9Kӑu"n>YzOr}͗ҩ8u( )wE9O߹?| ]l,׳cOfA78m_ߣ~uYfvglr85R"|4sW!Da…/qN7`6l& Q-it;}v0 9HI^-a10p @i>WLWAX|TZ0v w˯DQgaŢX~{W\3g.g/({ u>\|h\_6a7ݎGD.2 wx9_O׭Ν;S }|n!=qo@vZQcοCh>`Ƭ/edxg>}0 ^]b_!0>-)=,+ cA0R6.I+=^ˆ%R"t{҃kH)ޅg_v_ {/Q,ۂ)_DA#.7^f|X2 Wp+*{_k :%lIBϑ஛ExK^mIؼ-[wlGxB2~axWf'|D'x˯!=Nz5\r){x#V|''x*NȐ g^6gmvzFEia>B1aa ŭSi.k֯b 2_dab"l\sAg\'O>Ö{벣Y=يYN۟LG.]j,TyF9)W\3. W]zR7oAnN"ɬ70[S עBU0J27;ÍˬUزa2!nذ8Dq=w`Ř1wTf>z2AM8wd2.\ Ùu/0VcP -ņf.Y܌n?}-2yd<kV-GaY% ,sr3Ϡ27y~9 e;os.< *-SNjO=} x`+^ &*K[A(k2۽]y ;Myx'pِ|/4͛T,[P|*ElVVrq/#>6 ofBjz"9|$E{a3՗ˑGg _8j;,m0k>7؁^#ˆ#6r40G,ٴ_.^sqz txf1I_+.F7yaؠs6Yt^9lKCZfi@kxA0/w`=EY(ؗܬtdgepsEuNr4J6b!bL~{6zXI*R'#:<q޸~iro\/JJD7{J3P[ǁr^k f2'غk#(T`ǞlNJ/j.Fŵaw=s?}"/1K0ii3s|r ac諡j&2ynі cF^-f+ouN2N-D x)fQ#Wf`5({¹Ý3]'/w7MȜl^eUU%Łʒ"Tm 8=1NJwVcu~)xɈk SF,N/}aP7ai˓oݻq=᳟c9"ad?ȘGir,qAfTY DkwK F~>Kƕ S.up P /N|muFQض/^FD5mμ_^_[ n{֌ q- 1{_ԑԋV"t'3P'f`aH-t#uUNuZz7!/3βq)J3R? ^{ ݌g^x 1=`a;keD܀ 0?nƏNop%c#&c]q=?!$y5.e58q8)h0Lnz 6Hv9mfXd6^ߺc(X &[1,_Imӕa6^6Y8^LOCENXDTDܵv8p Ź\h/+}<)޶D")!Ֆـ/>|X\ PG'B yk8x^ZM]X> ɽPX\wo?𩀏&o-XbϺ`Œ9MYի{i* tRXڎfOl]>_LṧN\~umDB;~lȾmOK[V2cۖ\ل xժ9}>n{vV282fpe,^FBxk7w݅O[ *p߳/p1uV;mDDZ=_Vj| T/<<|𫯾zy7V=ԢR+-Śիa 7[*]uZ&mE/B '̞A2CQ(?Iƶ1cm&G 4<GA"#ܹ1Pdyo^^k"!![ Vo؎P7o$^ؼ{䨜~ܫ0\())ztE^CuH-ugRgZX}7"w "b(۰S$TsOҥ+``zt`O+.Z HE -#+mpoĒĥ/@ 8"y6SQvڪ8II]zcܨ!0Wc؛QĞ}pƈ(+܏~ 6t9cGWP1X&Mw饗Z""" Ri~ްxlyE%~(+-Dnn7݊s7Â6Gsd·~>jnJS dԪV_Z6:j I>=5ݬVzN+&&Yy;]N {H7/FhX{wy.jej"kG^DZ]90 `vbꘘvKb{;@X;b4f 0S!!a:< {_$K ޣZܙGI..kmD4BwXDu>GS4`Aɧ1{_@.3v)xMqWju+eOہ;w~ '|ҁ^}G1ݹLzoKҎ,juz uEÅ5EWC-϶R3n] FZ6U#wNBcRԭR81 sQ`ޡSʉb˛ e`0`0LJ7S  8Ūj@+@Z95+Kvܼ7kGHL}:9StKZj=-ɏʼnb6F%a˱b +ƌ'6oslڴ֦Mw㌺P*ttjtw$rӮ]PPP;xE!IΝ;˗/w5%l۶|ɹ%pw:QI]AP jO//J seen 3a4fCg3O输nǂ rN&RP.hPކ1`-lJDb%҇΀N;Ύ;"QRJ={8LněrMʼn@޽{8x4N`h`yOD9N`;v8#N8Jy4S֍200`` f@ j`NE X뭸ENTʊ|Mى\Kli)[7E@U+~@Z.>.d Q^^ X +* NeHOKcv8/@QV&pa擾]ʻ۟'^EU%HKKG.\^vRXn+_T*#-{_AOY$e PQ]?+c2 wRċ?ggd|; =y/Cea1\p=?~;<(`r/Yo?I'yOjx/Zehط~>nf|b|Ѐ \H'/C#qഡݑ?= A曮FTd\t#yU$EaM-Bb?a>K?휙EϼXp!J=ld</? l+5^mBh|o8}pxD^ }C1pP"U ]i_wce؝]  !sw -yMsÆR.TR(.)GvN7R|rː[QE-y|Gb#mu~oR`q( .QsA:gw[E~0 UKS"b.ܩ4sPrQQQ E)dL~.؜{P=9H۶g\~7Ιܭөxi?? p/MD[~c.[}+V|Sx惙xꅉpOL[W>_N/~|o"w<Vbތ^ܯd0NO65ϝMaؼb}fvˆ-nن93#~!zNx[)2PF |(m;Rl ĵ|>Btl^ݰNz*Y|UΥo¿n ߈.=>S7rgƧG3}DcG/|G-5jc;Rn\t.a=}ٶ^&n e TMdbkJT`ٖ]h۩#l=68xG:qV +7W^2*6zEb7s'M _ukNƇྜྷ)e^x{1ܻ܏X(Fz- ڷ91n5Xu/F;vK);&E=dH/#VUg#33I"88 y g"Apə'{|uzc%BG_搞~ҳn>g.D[%B-({U>,Yj$vǣ=T_d"^[ ί B%xㅧ`rcgj>:t NAb|~Qe"8m'\rf}<?Nq=;pzM7qjK(Laٚh9.[ʳݭ}՘cρ0|E#i?NlD qqIΚ+.UeE鐄!g^e+ݔ$F˜+']2)d7n|e<ÓނLy~k4|?q ^g!6,kZK?sN;oJSlx0 `~d>VӾ^uϛ7٫W/_Ҋ’ }./OdpBV^ :vF$7urZVQI$=/nRsړ]8ȇv}vF>fG'upwo/(i񁿟/iޝۑ_vpd`߹o,1c} 4wEY_$'{3,"27m壼~H 2Vn T*C;|菎]ȍl֯jtK& {eVۈJ39A>'1cnm' etxbuѣdW$vzW.hwZd@chȥ/$Lm[qRǣm ʌI=jA!vFD'^yz ߀ ' ڣgp@)c'ĒKB߷˽!zGW  Zv&!STx<*XU,.c/B?JP橃8u(i00`끭IEauU&$$BB=Lߌ@=aNVP@TG4Gq)$'k|7 30_"pSЭ̽EQ/c*5  e-M<)S(5nܙoÀkKۖôՑ0`H!ÿ%a@tɡxg57(YcmZkB*^#n2VVVjqy&ÖTޤ-.ZˏMt Pځ ooo(쁐:m1->&MrMZܼd8gUGulZv7dw)Z«}uo) (()P/ACmv7Yw†& 6{̙3{Μ9sm^h!Ehߵ{O@.Lw -D5/ d2YHXJE2*Ia}%,'+@vCdͧN\Ҝ>au޺uG޽kKJJ$E޳gkbbFՊ(E}aeXXX=!l ))K)l\Yt4txaV{~oK G^nmB9̔zQ[, :<دuŎ'N ** gV1sN  ""ߠB [P;pӦM &9%̤, (ISFHV3dPحRH#?pWTJy#99Ac!k^![x@{[v :;;X `yZZ V~hc.91Zu*[A.O bs +n*`{s47qFkmxԞ9p&ivf 2'[[!8Z6Vco}78@ckfʅqm埲{ʀO +9o=9К3kT;Wt`x.~^5@*_7&[ Mؼ(zšF!|{XG`aPPQQWOoxN@M|R3XS?A(7\Myp@@CɻHTUPdK,QB ! 7"Ŧz#^U!!P]L:rBk$jB# 3zDIlF #X܈,u m +k(Jzu rJ ި(EzF9h,4&n.0SB3BLs#40P\Zh(OZGdl NB)c)"o\ =3 r NHѡШkiJT*(K e`PIUqXȷ,ξm̟9~8P_[QgvFػj@n=.cM(mp8 7nDnJh<=?y-&aB#':n|Fw>N:m$pZ$^x>z%X_)/?"7~Ԓb~/dgB:3\ *|&<o>Ðsx;I'hc  }52}<=b0Oi('E$ 'o:zu~=ڋh'@ /~{v,::,≷>?S;R;ÂrdG3Vཏ_O_|-3 z> TC`TAV0p`tL 6`_`ݬx ~:z|+p8*D )>wg`O3͗cߩ0Y44説=5w` z). njLj ܼc{33AGcm<wQ r08c91 1p<}.] GF{Q&2Aw>{ nSxc4^n_½J$6o \BۏcӬi3=<x/#}zL~+ ?dys 8 x Q ?bXz m` ,ЂrugY,UXwt:cO>w!*~SvSP7$޷]0gP_*{FCAV, D6b^ 0rp/,9sHf# +ȴҷP=aݡ2|Ȫ`ܘQMB嚲Z<ʧxq1O1\`}aB`l;ySS@߼E0goǂ xسYVM+V!W?|\2Fxe Oe9@/z%)C){( JCF j"^ F  X?% ?<䐑֡  %%;h D#ՅHGǐ6C_%( [5MOe<'l%l N)58o7t&c#YL4cm0BnVB;"ٽ*N~if7^Gto|l( 5$GžݛoZaCU/=t5c$u:S3/Ň&"Eư H[HF B}HO=IÝ&0>} p xsYmbOc?R+Wo"{ѿk tc&C0fϜj?${as@?ɝR8vcwݍB%o|Db,d}Z \mu>Bl;N@Is(UؒQD i ŏطfm-UQu}2_|J 㣩?oWxx>$"B|酉ׯ>CS:wV40J"~;zWsDB?ވP W (,:));&b_aL>rQO4ᷥs r KCn' (x8c{oS {P`'k-:m}gZSt;с E S߈ 3B*|jEwXxWi\3HBg2q4Kbl$q _/<$ !drۇ^yԛP=ފ}Bm" wG g;l\U:9`0'?{+sd)(ziӦM&,om1ޱF&>i]YFfc&`A. 95YhK@ U-y䓰vrq^"Rșc 7- 2̸> ;/|t7nbF +&OnZCU_RVo0r1Bp12N1hӪQNJwOl ][ "Z>n!'GF1 hZ^[)NǪ [9}ϊNW&BӞV}k Ya;vp }Mh@Jx헐||zB]WG}!y2qӫP^[Kʛ;7r-#;"(W/ƾ,c5DbOsػ">)ÑEP)͢ 9Ek,%Y4o$zUVYF!?r_ 9JɁG" a4?-dJ? }k|<{D^BC|z)R+ s IB2(].1q(*vw";#u9 SOZ Gߕ 'zq[X/ ~ֳW$!,{[I0\]p)^gΊ}0x04 M,lQ&[H8lH :'玙@Z,/9->E "<"TL K-f`EF}:FPhDӳ܁P(KQ([gDbmi>tBo%(9+v=<B[chcq]6\ JŞayalz؇3Nsl|5͞Rkz=t,xwEK,ro'$$/GbǏTP4q>xe>Džyp/N'ܽ{E.KѣɱRxbK `/UK8"R";'tss (*#((S.rDDDs+#P8\8Ł) s~̙3g7ݛGmr ye2o8PHYXk5F1gEC1l굷rXj7:fa›}U9+L[ ԬX?;f6X!+T|e™"kڵe #.Lɮ.8pq;]sYp {\hXWqR} 'Ykxa11_x4@Ū 4w N8+v2<Wfds+ρ:0M@K9?sSyRk2`zU#öoW |ɤ֣ܵܚ,v6\?S:]KZ z=4 u훗f㨣yoT7xeLJ*9PCQE5',pfCVAW+?}uoު00=wgWZ98^bٳ\=31Z敕ϲGmgu Uw$zY:m Xc߶]f+l{?G3Q]KT6X¶W2N\{G~9gcv=U4N 3MevMp unw9 yК$MxZۆ%DDh4u*])K*FUy5?,Q[EEq1 w%!]hDiUt&e(*/2^EUB킼\hfsaaAp 5UU B7bEyV$d2OeNgB$y[ (/%D waa\,ϿHI9r3z̽exYY]}bGW빀=&M9н(IZ]?'"CV~߽"~TO\ R;1_(û2345ijvboU0f>LT1V @PJxeo59pǀ>ɐ{Ӫ7oAHLw" ."<}?/t w CrBX<0{~_!+>,Dv~)鹋BD(UYx﵅?9\qDŽH$&wkj]uMpFl/eˁwc! hGD Zf!`(>9+wd:gwAϱof䳨.em8 _Oُq<8q& %tǗ`ӑ[G@ m.̘7eػq#:Cʈ' ų"7-!d .r.gBי*#teC_& h.;}=1Xp.6;?/@8QX~=Dqbbh*L6]]n«O u$\?4v7SEىxyڣ?<5wbӹМ6!qWݐ_ld{Fߖ./C#6Ҫ4,_L h2+Eww2AGZ1;43 ^Fw==C }WB+YIphˊP@Yo^o y S tGp|8?NLnζ+O&ͅ4'>eK7nv,VLƀJ2̈́Eg{F=ekZ߈ݛ6!!ڇw@l'=j/oJ_$u|1k)&0 }:Sf;?r Gh_GᅏT}7}{ Yw4Fs "̙#db'tLJWM]i &(鏣dzG"<Ѝ 5$xݷ˜xq<&C!$A[@i55(B9^[Ro} Ȅn۳bboK]:%Y|3h=LHO=Ÿ3AU#(9<s!zJJ"g #]6C$p30tJ73g#ceojܷ 6dH &Z)cKc#HCm/?C&$R`$\BقI4n9k 7J'msY5+{1h,wp=H )ϩ?$}0,=j{`ך 6#c:`΄ ދ.E~q!6l݂8_{&?>>NIöbS]QSP߭'%'}W r>.Bt|k fMy;Vna'L{`.]8q.ZN]?~O3R<\=0aD/|G>n t'q 0 %8:j*M"t5C'c(=dW݈w9E8:U'Rxx{C&w/9-U$xهrA49]N"\1_/;r tE?LrHe>蕜yK~*r NBsiſnяpƀA.$d "1L ey!LQmbg a߫ѱPislS-tСPHXnRuL 5QރRz <em#Azf~WU}\##F@ $kη4R8XԘkX,{dsx,܃i{Zt/y5bL@xeSd!Pg^$  j&]$v9ÙPK!(>! 5JuE)DŽn̈(.,"FP/WVf 6|19.( D#]JVW+[V+~+XuNcwP;ki4u{|2p㟴C0٬\ 5W-&S&88|БPAXk -uT`Û>g`4Vg/<Rq2`t7}NtEyƶ&gWwg@ERs$E$DHH IE 1϶69UXJwOXSD46cz /_Տ٥Duy LpQI~eM@>^=i#?+Vd 9tdS/^E4 J{u@AНI3N@5(W >7sg3;lL/Ϟ+DH<$t+pWnQ  j[U[N+倽u̚u= WCs9@s\h!Z{<;m'd{G^hϣwk΂-,|PCc&؋ѱIyZ-*k-֜6%%%=ך}m\QT2eRŜGs3?p6S:R"@*29^i"j1992j(! -d/5{)eM/LKRRǧd1))/thunΝ Ddd]Z.Z<m\ݻ'sfgK &1ܽ{w.a ^ziSMĄ7{a`,Dbuofc`2fꨣO }*c?D _. \s39uv0ń q:le^r곷2:Y_|673>m+ܭU>WKMCcOZƄ`{~7W=lb:Y~DŎ9+v<8IҠU֙ 9 \?v&4یSuD$'D QMA؇@DA1 :m=Hdr(δz7'C0$&!\"]ᄋkȐ/mev@DL,|X EeVgBPDV̪!8B&ńh-5A Ia!E5/62Ywl{H"\ph4$@$#IQrr.΄fxHTUWz;N*,|R*u$$VVXV%uĂ $:<NKY} 6#~iU8PL8p8 Ikg>Tb.5Hbé:!<+$EB_Wu0i*9t !>[_0"Ecmq[;:Fⱉ`8p*`hO-\ e@1qz9($:y)2D ƭxd0s9Nf硤^axVf91tHN ìo#So[v@uM5:Fu? kTןBׁgP̞= q=Ry !;A1ĿABT0) n۳_8Z; C{bEB^^. {o<@΍ Ɨ|O^zk}QNű O쀆B|3ku3`)8PЀ#)ܟ X_y jD9˯g㫙bO+Og|37|3{ st.AClTa`tC ,ڔ^y~i?)%'/g?}nMn>\?#;〣7tIS?r3 FV7f$=݃ӏ\宸)5'SsiŘC?q*9'EH:,K>C^?;'H:wq)p:Cճ; V8Q'?CI9Y]lЖH'_|͚ X<+/Ys0$wE)r`x?g-6u1e]OΞ?`g!3yE51vNAʮMz'|x-mcJ#<]s\ٌ" 2 V  Fڑ=t!ѐ9zSH %x{Nv{AIaٖSdA^-H\ 5k (0p/Zf Cw5!0<$TJ]ݱ ʫwh"BC)L렪.EZHѺ,ue& ˆZעƘMy!tWިlmg;. Bd5~%<87d 1jx,ݴS[d a])̢Pn`{h1Җzꏞzt $$mIrv;Uח2Xӥj8(,s&(]ȧczYߡB J[>s, g9Gfkg&`႟qmi:"9ɽPD .|xݬk2p9η׶ `0l^80S8v>G29]UC/r}܏qȭi߾|s,cB5US'!$veUu[o] {Fzy<]H{5޵MT4bc"OdКT,^ ]`+Ēѱ@<06;HݜihQIV\.R遾n?Q#GS|dkzO |>'Xo: a=qA LSMϷ`q(JYCe@(HJ?G@NjZ@,}z5Iźˑ[ 70lx<|XOWH\Me96m؆#)hWu.Vۅc)؜NXdhޅL_U,N zyu2'Hψ0N X=0 aROboN7jJ ̨ iEHN>X`VPa;ɲYUwROR#DsӏǑ֚bѕ8q8Ըw Ƀi?:~^P ( 0rL(]&#cOs $Y ?s9/+0^6#IɡO' ~Ǚ\G 6BYhqqꫥ*)Qޢ} Ǐv35 >t^` JmmѣGu$,177W@c/eS>''ǑzaPPPCff<00@@ CCC!kR(t0**ʐ@( È2Ek B{) H JeqFl9.bDTˋseĥ>kjjXHIk9E'Ss4VC z!2eq a{MB [J)q-5y†c휀 afIe6ׯ-Kamo6FY\)͜}s:)u}g\c {J;R(`K pmOw, 6jyq 0 _+%ĭJjֺZ9c4Y?-cVFs2*ٷu0Ez r9u&YaZmswnf?V3u0Vy |<Z)-}.YkkmZ綠pY*0gg@1$ S=qms=O+ρӊc3Y]\[ǚ:7?h MYsZ9+99䀾^Um5t 'MTըo`wקuj% * nVu;!uk7N9xZ=ƍG셪tco wqfP]UJ4t(95!ٕVwz=]UUZyZס:1X^ TPb4ӂ/:H:][SRh 0wJU*ΫP-F $ MTWZ",#RO`9ev9@kjQ[ZƵ}QtzKJJ:4 `UUju}GqIY>BSG|n^_rØ(#ZH jz9qUe‹y3^@Ӡ?nzKP;lмΑ}ӹw_sErP]DXeU z?$7u<Dx_ a^ :qj qʕ!.3N! ٣ 4UŔ# b9WIIԢSADZS٨&堁u^ѥC!^c&ؘ@38oLq|RQ$ԃ)(8SF=|ܸJؾ]{"<wlk@58Jʍz;tF0gϤ`߮mD{bϞJq=vދjĢOvhm)9~PtL]@JP5 db;}ڏkJұaw&FIc5ѱW/<[ зg'8;:Rg&סNWOC0fX Nbl#X[@MW0bPHi3͑vP~-&v@j) u9P# ˆ]w|?vghpx'뤸ſiFCw`^ P,+BNq5<= 뀏 BI: Tksa^ >7B<]E:wPSixg!:c~-^ʶ8@u؟SMJGO3YPeW!휐>O/SZwT!/Ő1㑺o,2@Um`"K&aO/G]I}Qvd=R TPbͨH0qD?8Ԭ[+nFN{h1|0jGY'aѲ Yw ն->6CA+0x-XTL*zWEbjKx.Z_ >X>%70嫩H?ؤ><'>&*'lK:"_uW/OXbǼԭ"|/n1cǡ|b$|PG1wXH9pA"`VVݍڴ@;Jy9H :߶ mZm5􂶲YY"pN?4 ٚ_x" %5c -D;eAG}&BLP2_+*#~%跅buEёw7?@N}94"K2jN"'(>;?@K  oWA*c?ڒߴ={@Lf_C}d~C&6S- ?:]j)"~X2!Iɢy0o0 [c];",#z{`֌>î|CρKixBs@DzxD酾Ӟ1 ۥJvF:h>$4񱱐FSG&$I%ݐeb  #i~ ~_JZ^XH@{w˯za Xx9L8dx+0>A&'Kx|7xȩ#1>l1:w0_zTh K sԱrKُ˗ 3rbH2ə9m;oDIF!SbK@IFN%贃݅j9]ՏFTt F@@Drvà&G::) 0[t Wk1A@bt9҉TP" 实5׮Z VcO }߆HE/Y!TqQعiBw?r pX/6ޠC3)x0/~yY"*YQq#b͊e6t.vIOρ9{1@*A#FMj,׺ma/ 88O]{=!E(tDXx(4e(>d@dT$DOk- t@KC K(@MaM H KiEi[Ά|g $2WuH AqQN?c_MHGeyTP\]e0IDAT 'wZŋhO3v?ꪑFiC2I;'Q`Fd|ĵG ǎ$9/vKNkT~Fګ?gNed#*v!W>WDIO-b)-R5<Ա)X zF(0NmQ.gd@%48,|Э[@;r(ߝX:cH uǁ}q2# ?_Tc%gV߾q N(  ⃔}C&QȂېKAc49^qR}AhJl{J-d ؎Эc 2ӳgAUp}w#QCΐ)x衸&<̝;w1UA"\=`+r*jEfU-kڴiǎ;6zs)9xW 9rNg۸d]z 6ۦظqcPcz;^B9䅗n`0ǎC,Y0lk$S93ssΊt2! ma=S[wXo 8Nd11̐n0"& t kMsGH9mK#Y-!>؁-悵x:b-xqtćhHF Ӛe͚56l*ԚZ2$+kuq.~+vw߼ eZ:Z"Au%E4N9˓sj9Ŋ]kz4" x9_Th Dbچ<_d!Ѵj kjY6tQ*#BLM \Ӂࢊ|E5ǪDuPbG0?LQaX1M9Þg5sm[̮>c=oֹskB >O۞ZSS="e9YK%1/V0QUUSTr˞ZXac[L3~ rrt ®yQ4Fsh+*( dWEܷ([N:̜t2=IL$3}N?:3=>ӝ33I;Y5Fmw\PTPAتZwK+"s>w}}5Đh4Vu X!A` ZZZߌVޕrd3IRJ^Aomm}k4LtШUN{-7"%y$0ݢ\jN*իW2 !v P&sxoI_ !EzR&4 0nIt6-*}%Y]]['ZEl;$yƱvMR%ޓ*=YQQ4i]%?..Y^^NHHpHѕTߎ!Ull,nTv횊@YUUdg̘qςB0URRo"yK|f'p0`N-/RGC|_q-|;5>ҵq-6=5 AgIBڵkUǹ'P%V~j r›G%B]~}/sQx igwה4ZP4yဲʸ)y=%O!&w[7](#mFeD%<9)`{G7dE"P&e(($䠠^j" x%y40ݣ (IB)*cԤFJ Db*U 12zS<fj3ݏ5jw/ 즣 ڐ0htZY/+7cP nE+Ãh:[F^vXTO7y!(Пn~] A˨}}JrƦF[ ^ha6>씾bD ;llZNx#\SG:.1ҽ:Lfn5GNhy_m>~G#5(m䉍ae ؟:($<~52Aw ?6|G : CVIhTkRV,a  qa'I)p\p@F,v;mN κ+(p6 f̈́"v؍@Ɓ QyaIXd!:ql!L/}1ZDi/.apMYi ܻ'dq.I[nw"!*Ehl6 alZSPK܄`T<DG`C8p=Hd8SakRcYdP% j!9u$qMv: h#eg^>UcL:̈:IBֱ.pf\ֻҹ쓫`nd? q:0G?{9 e-u<߄gɀ*fǚ'3plNB3\-if7[F@xSk|;3@,D `38WN b_fƷ{َ?ױcƳć?C2|M(~Wտg9mfؿ \uoyNDLE +b*˯c}|tXW[6+|"Ί'/X~n Mr~ߤڶz ߒce*uR%G 5& b Ce Re' b#oCƲPTVCMLDxf kUk^ n'xqAUmu=W0iT\>_M q"PP!X4_xj!RbQOr b8Ӝ!/q|)r RV=2"&!}Qx_BKgh3ɉkqq>x~=O $q@qF?&9 ::aL;/|y5V.86rWcfDj3ݝzK>PQ:yK+Mvb|hs5ުxdf/ \ (-)E9X 8/Ql/3s/Rcipːg* *|&qSk! x" FqӸQ&r !Ȧ-:>T4mű+MxEx0E%GZ* Kb^F'b&ҎÄV_\F3m>TTkTVE`5o>"i@31Z"kG@n @׊0?}G֠ r/5bƄYH|sAEOZ m Zi'`AڼH8kf/Y=lPFRŁnXylPp/xSZ (ű\954D|ͭF$' >&iG+aU^*LJQHQM(}˭ >B =q'«?{M#Qdœ ICk.a'_ֺj<;DmtQoTc$ .0cJ2"Hw;IO@ ǂPm!H"Gmqݤ (Æ/ rq]tq[nPSa㏣YOP-4Z 4jAԚ@4QMoz`?9 4c>#P?'*'|-$vdӝƜ:ٵAۿ#VRB6߷ SPcՊEeA;P{;^%^{q=>,`5md?ә{>u&F %`|lW"z` cWl(*,]-/TV\EM)+Ji4n,UQO^-#SE  Bd7$g/ͫY\̚|GDtM`]@jLЎb%B4J)`h`ԅc1 #b2W~{vLA0\>CE]5b!&&3M]n67bfE :p661I_t4?Diz?GODLBQiMҨMIJse/IMNz=|4$'L@{S m9Nl% &&ƣӬ 8w" F0KGy9_H]hFP&)$xDR-[}mK MF/ *^h(71yv[ݭ͹(f`|xnLL਩A`; s666V+y 6 Ax bx!!HT -H2l 'B((J66t^@ 0L anX= l ` G" [N~ ß%xEbAP(eerB( T%"ՋzC}FhF4/Z6@ۡ=at6]~Gϡ71N(Fcqb"1BL5 3yYĢD VuccYRl=&v;CXp"8U GN ▨dTT jS RNV6Φn^ӨX$\MfCC@kJM{5"kL|:~?K#ѩх;KAn'WB,H'yߣbx2D32\fdH(hØXxq S,r6kvBdQNC.1`Q؎%>n~%'/#o.!@ `Ч U'iOF8erTSثxD4scYO\ |E\ŇȝLRR5pVl]5guvzuJמ9p>?`rNl=Ka]r4РpQ)i]_鼪tR`K5kYPk\B[`v'Mк!QwS[ ; Yѷ^Zrgۯ{ǽgשYmw3w~݁;<$y{Ho{OL<zjޑܞM>}>"˨F>2J-|XݸxD[ÓΓ6? Og~_ACGwff}s/ٳUssm_Y;ȽXԻlv%lUպ5յN7b76KĶ017 cXo,7/7/7/7/7/7/7/7/@  ;9<-+(#AIXDšs6l).uM ;:B$+FSN([g8W7yWi_HA0C^9U HBIe`Y + ۊ[JK*jՇ4z4[9s|R^~Ai*Z:KVmFm?٭:`{]ٺzwH,]g Rz-l2q`!d"EŐcm|cf%'6&uz)e5 JgT:b}(2o . S"%e#Kg Ug jLYڝuI/]jlo핏WZ]޺_ t$\5~tWn^>S߷?qeO * }t|hkOW^I|F~Nы^^1Fozlp<-7|$67~8cI3ؗk9s_-/]Zo !P*L+Q%t-c­R4ZI]aACL`bK`d2NBwF>9DrE%KJKJKe6d_]d,BPWRkV/Ԉд֒f^3N>ogi(dDon:<% `Їa)B­##9"FwfMJ8s=!:8XzZFL#Yj92Gy'͂N̞95U~I{Ųӥg**^Yw~OLX FBef+ZW [^OQtK k*vIyߵgx__lCAG!*O^xeūQ73oN߿;缨Ƹ_3@9lXp:)@X;U{d@!lf-T!p~ T@;RsH@j @P-t`X cx%Ce uhQ7,z# 4bVdQk$. 7L%AA5IMмէm D1 20032_%*3V%M8x:=xxyGw%Hb *U ;<-hL&I/˅&RR|Ԣ|F%[5RU]_CRQsIvS:}&zjRFDcFS&3Vs !K+5kCȧH &n)m^b!>-~u@MZmpc(]XQX%Isɥi+{TsV]8ADiJ5s5/ֿjihB>a~Kzֽۇw؞c5yiBHu:z&]M6ޘ1 Gh&,S ]K9:(!v899"GxrxɼZHf % +^.%i)%)&W!xE%eMEUi5qua AM~-~m=:Bd]1=I}9CIiY+VompvQ3{ܝ\^ٸ{ y1z}/x3>;FB90!y,j%;iDäGɮ)iG2˦͹vtqłG':N]//XxFʭ:\uE3W{`ٕpǃ+CO>yƫќ I)̇/'F|#.Z.%,Wt~Y <^:$paϐ=⁔ +h?pKI1fp\߅f/8>6FGc0ϰJH q̩zoE=# e F kL|#\CV4k/[5'|@}^]ЮIR/H!d&&"!*(F%'),%-,+k%.;E.Qu5.um |[s:|d;L%CY#UM3#bY+} 1;C㲓s;W|YOS>ta~*p0ʍpQїb5'&yNK}>샹~;UW|ԮkEgSjν>{An&cW*Z:P))%-+sWɀGG|7933ef>rpmn§o}K+龗)5Kn0loLoZn6llm6vqv@N2~6& v"GO=Sɍ\?(?ypjp l(̎`?JoyR|oy*{ 0@@Q6Dh[}3 x*>Βo9 ҁ#wl?`Aa;k@lJ_0|A( 3@] 9Y=h/AF!cL?$?#$_Xc5bϜ䞈E~F^vFvϚhy"ۮB hZ0g;4Eũ9 ;O/p @/V'rXO5;O7=y_[oWQ"I:kWd--Ińd^+ pHYs   IDATx]`{$$$B"QoC<{>ł H#EPC 5Ho'Ʉ\ lv37g̬) B@!r0lYS9S( BPdB@!P(Z8u͟JTvoVs4Qf+%B@!hTK2-K5qX),>^409?Ցzu$P(IXݳ7{#\v]WIH4y _$@9Oe N>p[T( @e#uIo{1"k>cο*!z ^F|,AguC: P3g{7d& B 9G9[FͲ.$kyjO1n+%{`gbGZ}:nKwɹ544WT(bCCCmƍP3cN ve~ϒ hC  Ĥ* B@!p` +t WK^jǔ3:S( B `NhOg!5I+#{~ {9B B@!h>HnfgG9 +#DdϚEB@!P(Ms34ԓd2WC]—CPX]( B@!Ф07|==y ^eeN8Oy(_~_uP( @#Ҩ^=Sz#R.ٳχ2+H B@!L`n<̈́_FUF\=?Fz$K/'aUd/=IHߗ~Y!P( s3s$JSP~E HMB@!P(IR]NȞ=_.P( @"<^-ѳt^+^WۤS+ B@!p Wn "Eદ( B#ȾʞB@!P(٫:P( "^*{ B@!PdB@!P(Z8[x) B@ B@!h(oP( E( B#ȾʞB@!P(͗^k`:}isԸWWF VՌ?ݭ󪰫9d|\W׭Ś|O~Qy(]0Zֽm qJlhX2.#ׅe/e8S^cgPVZޯ}Ly]ȞE0rHHH@xx8RSSL_NgddVZ! Z, o8䑝Ç#%%E'ېo\VruC@V\Xo@}g*W;$3u ~~~vޗɞ+Lذabj/n}bpwwǴi4◕!qpHLLĊ+77 {ܼyV?ccWrddn¢EMe#zj1׭MoNh:u /󵶏;MqkD(ɐ6-- ܠscѱvh4cǎaʕغu+.MTꚌn I&iw瑑Çs qٰbҍk=/il &&&|˞TC{^Y}hj>^xY2&Ά,QU>W(4dt* B@!2ʦ<#Wz%{&?&R&T33fь\ʣ,$j\8FGA8 Be" F]tz!{Pj<_χ9SņږLQ5HPX!P( D^L}sr`Q&{`oyg%B@!PܜYc=*Ϗ7''5{fȾ9E!P(F%L-xss,k< ɾȘ Y;70 A#ḣƠ)dt>W#cCZUFrdalD_:4K?:Nڕ"&TS(j^Zqg{f\Vٷ @\(PJ59u|t<͑ѩs e '=ktZJ W_+ͽ˴^z=`̹gg7w@X=\#8" Vvp<~?zm[^GѰl>#h5pk`*|PeXkGs KX"-s3SXQ;n0hfk5{]^j Zǽs#ed\(B%ojzw>%a,!p!&2 }߰5_S;u ֮1@(楗Z^C1;ѡ3iu] 2F%çSxT6ڿvr =8YIػ.%@RL K2Q}{"& (LǶ? 1->{=6UtVCx|p$ ر$Z>]}~A^I<_=Godn[uR#c8~.SjG+WD BxT_E! <1r8ؐ[~ӣOcPV1u/ueAٷ`E8fğŊr9<2B̜^N0*-fr<GF+'jSG!NI0t$<FsbO,Cd|. ]0_%_M+ ڻC/_K|`Ef>F>ǑCH&x0Srig'7,QX,V ԯU&{Sߋ;s~݈Bowa˖`CQB,C7<tq@ѕ8l޸cR+!=,GOFHtfa(q(1gb [cnˏ~8,Zg_B_?G3>_+2!z6ZP=Q;bb0ҹLq}s0k|A>eCmIp:&A^vDd&"##EHH?XrvybłłX ^%Q8:ϽL\/F D!g8bCLU8xbpB*4PE9]ځO ׬Y#{9q̙k+lbpoAA aae#fPٴRLGLx$vnf"#Q$3[tYPjo|K yPl^ErpmZ 31jkń'QO+l(Dayㄻxg/"13,ŔKd۾3";$$xU"&1U^~^VlgkoOWz_y{{^^$zv525.SL}lMI+ [ۋK7 ú 3``+}ODdRM̞ -[B ;[gHX<(+^[ÿmES"k+>-]Eo 2411hVfiÁ .w=Ū" _|}rQI@Dxi_,\B$eɋ2ݼۊ&ցڀ"]_8eN(U|քuԭ}Ŷ$w|i[{ #òKWą۹g?sϟz[v&x<κ Ca%w͊Oϸ]ؙ_ŒSDPT[{h󍖻EDWDAs)pG}ߎK?xWtq)A{rjegFnŬiհ6ԫ|;f͚%'}Exnfffffg.7$5/QeNc9v߬W|n7v6`̤yp.Ax1ј1y3Dt7B•ɀX| NF$V4*JޤJui<5寺3k gw -l_ ==Y(.İl6})>7K5Ġ319Dv|`1#b)Ӫ\Xհ<;O8r&~{aֿǠ)bc[㚚.}nd =`."%!Ιgqetk kް ?lۆw0^ό߯ (f36d4j';LF9+vAq~">zX91W;`5 ??DFNB,\9k`xI0Įo?tJ! //ԻDFF&rUl.>]QyD68 ֻ̌fzG_ᓷD|R2+Kqq&dfzg*,rcd߃gSЪۘ87c"9Xw$FӤER zzVO/=$iH=6ΤE/kPhO{c;!~<؋8IPV}1Etw>]_S^1>arnG]ЦOgӨ5F[' X Yp鄧 >>qix4f>}h~xG7Oo}>[H|d9} xtz|Jpi@} D Eks|x<߁0q 'OǸ^aTAH (HMGv埰_#wFQJ,} l9loa|Ω$L>=judhhlAquVx=G=ٳ;Rqd=>xM g2;zsذr9.gGaX| s0:WR<;ys^%A[¼yM<D֭p[\q)/,Y!FGմΞhpn܅*H?w ֻ?7-kbSaּװ}^ ?OXԎ  Q ع1^ڿ<v8X^e`c~v/'ND 2v2s 0c m7-JJ+Ŀ美/;2l?LrL9(o%Why|"-}-,JFyl8^f5V،Zx\NY%4`E;#1?06j#gӰ XzڹZix)!V؂1ݦؼTߵ?]esXsp4LqSp$n=~[SܱH)6cs\ }]:~8F{̾(tŧV`JV06|Z)O߅`nZf]+ύsӱnFa/?%-Jd\?]' s#/}jH_cي?1<a`_1ߖGsp\MP)]X c^)F'{IR[534~xb׷MYh][Š0ȤuBкpÓѳs[L|OCtwD쥣IB[Fոq;#dirJُ<HM.}0`@Yjw'4S1Y>cǂ~ =@+"zvFO]Ң 8~+YrCZ|A[C5Ƽo7L)ac1MuBK%hmQGq&! `Q`[*St>;vm'աhwfZ_7 5A>mDK f>FH 7xgt1IV? @VԻ$-n0|{W<RlXcg"Md>c/gZؠJ C{._&:HX^M2LmεipO:-_ˇb/D IDATQ:aDbRM򑜒E.KB rh7|CW_O2k΀ΰ/֯>mG7?tꇝFfȎ8P@FW.4A2A$i!6⏠#*j,DbJ5<Ғ 5c*ίa~CJyHK]. LL`MZLA>kLӦ܁~=:Toc!ȵNCŏնT@z~޹[yb ͤrs‰GL[ 7dUm`f>l$ $4 gφcO+֮j[MCbV#FVNR42AvJ,'ejS7áM'#V]BoN0'JeKdhֵx5 d+gPy4KX0w*.=:cʬY7Ly˼l;?D͘>>  i3K|\wߧ,ĝå;s׭ExR;.s HWTzWqxGmhto'j~miI6Q;Pr^^Wf.8eӽYM8#Ejap/Y";#DEgL9Iu4ݩ1.DO5-S1y D]:W9GGG%+Pwrr>5x~~>ڵkSw >|l\6#+=3L2HY+ U֣LhXV/unF;̬8T ܜJ,C|D'kɹ5jhEyNilPDu=C[o<@~" Š/ʯ?kk_^Ƭ'u'_h?ޤzB^LfvϬU!D0<8a]mq(x3?[b^.Џ> O{kamlM[5e׀"){)CZ(fM\iV%J =M{unlv&!~egJ734~2qa`qTX7=,ai7[3vptky=0~ֵM'?q`݁3ز;:Ê?ۥ\gj_gzBR@%o,(]?}A:ź|[! ,ˣ lL˫ήt3)H)5+im#='v|DZټ= wMλx؇?snV4&̈0@C~ 4 Oxۮm`mi֭:+._#rmRkldZ<$W C !+-lBB*{%MRiEypo][Â4z4%aDvM+hm$?' MzIu5u~pwA2ÁcQH)pE`omIsXX_H2EC M鶊5Uհku ri=B&}Zx&͖5QWq}+—K~Zfv2#3q"|2@qn~&^&{!c0{Hr`NX~9}h sRg`(Z?\[B0e w.N;ۙ"|Enld#8Y)*yķ3aU>a@/(W)neFV̤ZQi4`|v7~k+:uBQn!͟[)'f3|ʜ]\aUJqĞO2 07 Ͽ!;$n``6O5ͦq% 4П>|v$zܓqd^6rHj[L&%g"_p+~֭}{w!aoC,bևhTfSmM-ggv4ݖNvv6}3iGzQd0,ZH#oe5 k=:dL^tӝ,xu^ijjx[kZL쫌 VjC]v^0䎉a㰴Ykk$KYk,9^3 c'v_@||¶ܫ&Q8ڙZؓ])8䴫 O/=Hú9.K ;аUn>шIVs=M[g-:6agMS8˱tm2Ի6T#rZ !7<5A 8F#YT.O hf2~}*$903 rpu]nl=hy'𪕋8Ǐ}sVeC{t _{ MhC,d\_^l o[g[{Żo_aE#w}ia@G9|Ӹ@tYecsqmВVt.' |EkWg\Jg9b\ssgNřá8\ShH+JXM]J72f[CDsϾ'_zchm_i<h~_?`?'o,Nuu+gA+{{a ߿s%#5![ 쎾eF&'нbJ4Cyq\{Rwoj:!󙉞Xq1 c>!"6~}Ѿm?Ozn{/8=%MHٺ^I+5^l[@\zK72@Q7؀ LIE>u!ُ}ydgoVboI;Ιx屙hrRTڋ"4'elhA <o>_(]'4>߱&j c! b K6o rпG澊tJ|3 CGGxyop>v2:#(]ۦWqWltCȲXr L}O= =q,kG|J#LvΛ9N.n ^1c,?w-,؄?y?sHxwp-AxbL8ݤ(H^ai4e])X[/jFbk̮OɷKhs[>W[ ";)wx"pk:x+"VikurLVU٣ O O|||ooD'^|U sG%~;iG>gͲӲ>^z' sy\ڏvxK퉏CԖUgR.]ƕb3t Җ1;<\i=/]o[G`aY:<% Bsy1YO^t%:鉗p9%N4hoG*4r?'Y١lUw?6&ӧ5_ILOG#X9MPF:2B p Ag <|їixݎrz\8vlMgShնoHD5r5ţ'i`>|}}I c.#ɰ26EtT Gw_2d :ўXQ~&>mWd'c-=zFG"6=~ѩ|oO|bv4uBs&^ձ~v1,(͞=[! S̓HTཥYmyЂūMIc= =[3'C&qơCf;8iճP1߇fUV1a 7Dǎ5ҷkR(ccMRI-RNeqdAv}зwlȾ2Yӽ+$M&+kרQ#M J4cd Fɪ^#9yBh,jiwch;֯Bm_\{+dc;Ⱦ7!ڃEE>/sC`-YӮ:p\k z;!}ruܵM=kH0t[ڿߚ>T?;̼^^jlDʚ=% ?*m` e A!hwú4LVF˚n5#`wه7In;izc^IL=u> 6ȃΉ"*U MYȅ7Jz `D:ztA}Z${gg|#o..::ZeJ,+B@!P(Z z!{ILLִ=//5ߤgk|^W/"C!P(@= d)5e&T2k [ϲ ;=˫B@!P(Zza7Idqw>7;1Me'S+ B1+3r ئeN ˫ ( @sF^{KmIgކy]l,}5k|IROP( D^ d/5{^III &9s琙 Mg͞eeWN!P(- ='/%CF= ^\l/^7xV/GI2f ${!8{,Om RCe2F\/L}/ ;<߼( پrֆr}Suzeჹ.#C^ɞ+3 2Nz'}ǎ]o@$gڐӷR&:,L,~=4kqkYMUEW|Cw3|͇r``ܸJW~~w ة_\}{+cJ&shW^7=gO-sx7@8CLLdϠv6\&e&~I,'uWxf;?s b3YcLm0 nh0vkN9n9a C|eݓvR 1'102n%s= Kbg gɑ_9g9Nek'2,?/2c d#oSS%1%qkJk䫈]_uO!Pt_! r#H``gNk)}W笲,$C[˹Y nst!grg@2qpIצۑO7K.'rf.Ɏ +\'r F@-~w嵬k|׍ĆyyI*'pz%{YRx͍$CCn$KB`Z+k8nsTVĝ˿{rs>u"f|-ߜ(\ b= ՗7be`%}IjYP;3ſ%J#,(Lt4YS<~6CMyR n{]1S3TU0d]XtXm醥|㇖1WH<%7vNN,0 ϙל!&w}iZB$p|@,2뵌+<>g&{v/B]XU}3`Аk|u\P2s|-uc:Y8N ,Аt+{|W"os +KVJǡU=g'oڟ?>/ky1u9#rW#55Uk7iժp7xÇ#%%E[!LaGꆀĭn( !rTvHbgԩ~b/=W&=>#""aK^owwwL6M#~YIB狏DX>>>pss7i͛7kݔ&Nَޭ@u <+ի1b8;;_6"78u^x+q;Nz%{&BILiii?~1\[n]wݥV]?|I&iw瑑Çs qٰb>77W;7$qYp0 (qBz#yM+=< ΍wrr2x]v;"o߾ppp@XXrrr4XTR?. .,npQ5RAh M${>חjJod_ &̑הiܛ-uOoJ `Pd_S B@!s+RWٵɞɏ4+6kj}˚F.{R y{eP+ @!D\/Q/d/ {'.hpqqi:DKSf#SSS!L59`JB@!RYlYpz!{p%i>^xY2&Ά,QU>W(4dt* B@!2ʦ<#Wz%{&?&R&T33fь\ʣ,$j\8FGA8 Be" F]tz!{Pj<_χ9SņږLQ5HPX!P( D^L}sr`Q&{`oyg%B@!PܜYc=*Ϗ7''5{fȾ9E!P(F%L-xss,k< ɾȘ Y;70 A#ḣƠ)dt>W#cC?X&Y}@/& @1+ETL)Px80Xkf67' YF喝Ğuxtt};;ok\OBV c8~>].S'cH-2 xxq9JM6~3fv")#ER|39q ](B!htd)ɓ{Z=˚ I]A2~z 6ߊStd]}|8W{`(>{c=#cWP'g$%}=}ٹM ] D_<ݻ6ĥ䲊܍GȾ]ؼeү@qWR̨=oGH̩C[GTlNk@@/`L8Y@Rh ]h]&n\ҳ g{n\ǚ=zLgo WDD~91 &x7}c`|9`dhPhDC0 4:f4uP346%hƄdx؄e LtCEoETLӼ:z#C{!+./x4[ppRsɳ=o@LL`^ O RaŇcTL9=Q\k'1|k|Aab`Gmk77rkac92gMn L5 k Ushna Zenf 2=sf aa-` +,WkZy{F0%ȸQ K"ռ'=,}P Jp XNCZy{';5{7CLe$aƿak2.8w]ѹc&D/FQ.K/!!>bwC+gҺ"@d1JO.pA1mZT wz8q^ w\J*F<d(' ELPm7AbZ&\}0{0" ląb n=:^I>"BcIn}P,1x?~{& u& FKq\4=]'ՎV<j NBZybp!-.E{Gֽ0bT?8p^2c0o{p̈?Hrydӡ/9`TZ2| =ͦxtVN6ԦB0`Hy:ĞށϿY\`xJ⿚ReW< w^r "A| U!N}#L\}OaxVN8oY}/Xxp_=LwYH;w+;Z; ;+-4X nx9〢+qؼq#.Ƥ}W07CzkYJ>ş|D͠ٻP<hPb . :1apX8£Ͼ~8g|Wd C0 =~'l&{v!&was`M k3Ƀ|rk5ʆ7:ѓtLV0i"--MDFF1|rJ5!Ŋ# E8J7ptn'{w_.BJq >pqqqbb…"44TDGG Z= hꡊosïYFwDLb&-"|bϲDߎ˟Y9eoYHJ/I7n1kloaej\<Vouf&FeV Jɤj*=A89 Z/v6¥ ,dCtq%j)H125-wA? ƉR4?Y101U+~dSό+Y&[a-=śmW0w̚5K۷O ^ Za%\͜\nHk_Нr1;*t퐿Yq)n|"l JI31;j]:"@Cc1c}gf)~1,v!Vo܅+%9﫱hH4i5&U)ϕ(I֭)Ҷy j_ugq+K+Z7.03,@zz*2P\Ta>lS|o8jAgb޳s׉hbGj5SUӹ iiaywﳟpL::랗{$$BO*T "Ŋ )""MDJ/ %@h)Bz#&^ I3w`o̝s޹3A 7$$ 5J#|,,SӁQHϸ~:ކ<w~9r c<={] eN VPʽA6d4b+ޡ$?F9vB2_ } z=4s3mίs@VիÑtϗN_J\̬ggԺDvv U{s٨֮DZ\ !L!G#9%Ot򂡮&]!a K?-mu yU楴4wrr@JhMNN.\7Oa G!x1r@'G/cׅxQI !4a>q F<%gy  >c3 m厴g1ŷp1>"lk}0h , KޞDu7~o9FߣY`%hֹ jkh?_'4Gukx,]έ{w|l+YLյ3)ؾ[Qy%8w5lF|+pEhu3TP8q7v6ތ E9v ul͢ lV(# Oba6|.tm|+8t5ɢf|ΩD~{&ԚPCK<ؾ|NìOδD^؃M`Ez!y,|}qۤ Z{k$Ea5O/ 7o×UxZ_X`cǢ{prrdn.js%==❱n g8{js(8$>x9Y}s&&ڵ!JACp-* CAo2{ǜ݃>Ésj#.: GH+'D94Jr" l cCCCR3˓lmJtҒsPT1D/6߽"]A }{7|O&ݽimL^[?lM1}DopTP+ik7|-bZYNj|Sp*5A=Mil<v~.GvRo V÷_i8t6yVX:ӎ@CvE\5ʪȠ ns=; :hV.Z[7%'KltCM^qO|rF %sBlu! t6l.$=g Dz&-L}xvhL{–h7-C m؄q1 >yDd{Nhj_Yipz X0q1ZFB̜|}џ<wC`ÆM !]0fX 0!0`|4e\:Siuh5`ٱw7~ & ;/E!z"m7ayLb:/~> jvd*48KD/iquR2椆b&! SAfda]8VYoc j魂Xiɑ/!*B{ $^Db|*: lո?p#I*q3L,7$`͝;00lP7,Y^.E֭[EjwFIPPJ%k5?X%E®S6f9<O5{q)"Oh CyrEZv ‚:Gס~D\k?͉ZؘHE'];4bt<EE8tЮ_ ef^gGܨnnoIk k];.rDO[ jcشᅁ]ZD;9Z{P "xlJ wK*ײtǢO؈}M } i0ļSa^a۲?ޝ=[Hj瀋lר%kW_C!qr '' *̦5k`3\ѳ ^e4Ɵ"qs IDAT8P'R] z xv+V IXQzu$[ hbyOD/F {v068DʋQ#],xN>~t뇙)}`#N9DgE"o S7,x#:gCvXԶKA-%R$ү.?Y%!"6S54bqYփWǒ#V㙧5F!8t+&=w2Dyd xi +꘩.o"Ndώ{ =Ϻw69Q#@ iiiHHH=* =S}m*'/*ihĄX\)ɢq)4@KhiRz[$ f PBˣ/`L/Um`F+֯9m [wx{8}@aӥ"CB_teg M}  V|i~9Ѥ1߇/ !_g1Rғ⡯܊8DXBJ9Y,9 <(hkˆp|0s LqХCk2(vnY=gƤ |Mpw112Hȡf+W."ѥ?,ȫZ,B2) 8&D~ͽb֛Lf Ȳr63O%85;C캩m|_IZ9:iwJnfh~8Z舎QRn^a >u`nJ2L0yXulq3g>2[[ R}m_2bgЫ[Gɰ,4+˃Z7ë.\$ Cl\0{+Y8H)qG}sdeT ꦂBjiAB2?6Փ~sGԯv0`oN]y#&.ff(OʼnP$*2. Zb]t7c>A DzK7h(Yм -amT.'4ekC@WoNe]f4z&p|*=ܸs@pˉ%gbߞ\gc&þO7 \xjsfdDHK&M“ܻ nmp+/ .:l$RkdZId_sݻ7i5jlrr2RSS]ʪBҼK am=O Ф፾4r`(1bT ߳깆ݩ'lr9x)E6в5̌ y. >ZF :v G{gxy{?ībnT]{IP /UlM|u5'6i!6툕~fv5<~g 4uC裴 nD!5uLhݣѧk>p`N^LCz9ԋxcs{\Zy7X7U ~Y]|T i<(hjo0է!+K愙i} qFd&cML  Y* '~O%#4ɞ4w UkVtp4PH,Y0'i(Oֈҏg$%]8p#6}.] P7}&NlعO;XS=meMuw-MWBQ%>Ŝan{sh m;;2mhDf\9YaM3QWs~'>F#Zp7蛷-/Qe%ضf5Y*JFPTjܓcrxtFwvbrrak sj=jyD*}1ЋQԹ5z-pr_QLyyh[­] &v؄"KyäܫA/sCh젣oF~=:x iw+<"^ݖ}Hea}S٪ b[(Ci㜺EZ5ѭ{{QFMVzZTDN2ztHdHKdȥUdkᗛ,hkAPpu}C+(RtQ;Į ! #wZ{1|<3g-MG+oz}r4u rS$6Mhx &up#dWڥ%tmȃndΗfO+Sx![^ nY|?`ĴoG>2&hӹzPWf>Ҳi(cU G! As pkSG#C; {T*4W!58 8YM]T8?W}`Bsanz1hi ʹ)jVre="{J[UCtGRYlLi-t0֭ފ3Avi;iֺ05fpBqny4G Zd+mش0zA$c3H]2#NthMykIºiK0H֟vPy<=!$R 7N EZԭ,FӸc~ gg&ͺ KW̅ҺɱK%43}"")Vh-0 1wb ;ueFC!W~M/ +o[0qSxXfuÁH6/G~N}ܷaގXbhD(< &qYad=S0n֌=+>wC#_F>92/^Fsk6KW(n=3acH-4_Gpx* b+5ZVIVSs4r"eԈ-mO'P7^A;dԗd~hڢ{?o󰷗ƪUiN0!K'Y}y,L-C;p͊K|u$'S9&>cb] wr;LIs/ТF4qz"'pٲxNRn\w GZ?ECQQ@+p"[ KgU;il$EbWlh(e%>u _[Mڶ7(WVG]"|&{&TvfcOAZ\ 9Xb@Z{wP|6,&ZuOF ߶ [4xӤ eVWFHT̿v`S:#WUX2sk'G_h Hp,ؿs^I@oKgwRU iV/c@Yt2v"p5XHcvę}?xPi&t$(엇GK v '[6^YÅ0\\Lq|'/BOoZ75#Q4z;fE-x"J`XcӾ&ZzQA,Y#w >ܼBqUYG|&RLоu8cè.[<o$7}idG#1-L$$?qͿ*v>i,s1gy رy-of3pBKX]b_ D_ ?ܷ[5D$F-'\yhhBBjwŢא5g!:s?ԏN&z+|oYjM4(ߊjznc1%dɡ~<=ώ -_aDh(&6h4d3ѳɛɞe#yy7z!2!Тy3|:x`l.{}$Rh`f Cp]gOXJdfS>Y[D KG bSR`&$ߣ4Ĥ!/}\!mJQ](!o<4@^#8R+t|̸gp1hs*!#4w}k8f=E떧ehך_@51vp7Ö- ?W0% BA]4Ftql^r q_GM'}`oڊ;neJ_ҜE{ ދ(7xnXWQ/ 9þxѲo10oxXS@Y?5MW+)Mkp&|جS/-X=|hTd;1&䄵Ek/9>و O"77+EcKv+ľWt1 >qh(y(wA3t̩Z;>YO-d#Ţ.ٴ{ʵf=ޘ|99AwWm_SW1q4h c,:?1K[@3'={S7_ ANcu<Rp% Z5rK4i–0wkgbɜxy~Ojzo`!|#!*N4b%b.n$zY2Ϯɔ_*B⏍nv?]ܩ_G4Cq r;X 7"bՙlخz?W |5e/ZqdV=uh"IVadD?ƕL{E3P.\@Cӌ|Vg=3a}r- 4'>Ο?3fCjzk9N.l`Lu4el\|i{Knr&{!-crN&.k7h-JuGN`C?uTRbq;hfJMq58@"_mۣ#yp7ZWVD;woĬ$$?5Ʉ͎2R 'WIlX][ФL`C W\5|tck51))Ȧ1~+:hѮ#Mch[8иbtz Btr&IuiK^u%Sѓ@Ԙsl}f%_$NC\ܜ+lZ4?>yPJ sqTqZN L:VhӦ:DnJ4.c @f*9CQ7}piqt YЦwCeZCuu99IiE_>ωCѶ tUK"T ? c1pDZ_iM@S 37u w{JZx,=s W,Ն|ɟPm(g%FؑpF< ѯ_X"1R&j1.ͥiysss#k/dNj .]RrmkG4 jXعghNsfc%<JZEyi8yMc+נ+:LVe_Cs:jpd+E39n&\Z3⢑wZtJ|},Hi\: {mu_7tlU?~t"JfyXeqhˤTsi㹥Yihݪ!Ȟ ɞ=ԙϒ aвeKNMsYq=z_{o<[ʭ{xBjJ$}SSF%{el8}2*t۲r; {uo'OC|j"dmJ/Éu$gQVhÅ*nƣA UFr^ФYT=ޭ+')6kt0j`4c{/=UQ?/?Z!Dd#N_+"7Kg>:XK|M{Z5zZag] <^A-_0/>;Z(YKJ#VM>vF<qrhxޥ=LknV|AM8y8TWeBbl=]WдzW|1C РdD$5xtmcYhu$]Í|yP Gr":,Hf;?a! )c2 }cSrיRmoﱈgK}³]=Ջ"m=^uŻ}&l;5F2.k$&5\͵O^#@ eěؠd/ŭJL=vІ-Vβ:1?Af결y$K?۳eL[m\-h>wqR<$#A}&4-T??̼Z^Ғɍ5{^% i A62J ,@G@Ǯv\'3YbY5Y˚~nd !>9pr_tqPJD3{󾩐=w+-R=7Ndo(*#!@]GNzYz I8zx4Z"KϞ 󹏼8ьÔXV&zIf΃dddddBy22Ѽ<Wk*פgo|W/)5T22222 @= d)iLl2OLLl:eK{W22222ja7Yɞxw7vfcrgI)-@C"Vge7^III r!اeF +kAT~FF@F@F@F)#d/{K2(k< -s_ycZ-,,Do|%[>9}D^^yx/6[nI4ʞ}nܸZ=k,+ SiQ IDATddddd7FLL|.,, Y7Vz*+ "ٳ^67[ӕP'j!{&uU͞yA7Z5}wjU>,v%rܽ*U g5 cW8$[i2 NNGTZfГ*Ɍτdocc#/J2tPQ_?}.^(]ϖvd$^.KwpCwĎ)))bnOfܸk2(G[>)kSK90_JDB,({ɞSfڵXx1V֕$2f=z􀳳D,R&zN2e8503+cU_|V-c0߽+VT`'&(9WR޽[Cefe፹cd_V}LL<IߪU+888ȑHYIN_76e)i7&{e{$; b7kj27Un*#ɜ`kkkqku6ID}1~-_S'3h I_"{K-4.Ҙ} 6Ɣ+ 㾇"TߌTraQsٓʜk*'ۃ_|}6XJ @aaxN˚K7Hi˒Hcֲԑ۔c8/`P5D*Wޫ9ê>\ΤoX*{19Fåx=1'102N%2j#{O8LLQ֐YnXv/8]ekW1y [2ԑĩ10* Kĥz1eliKU%߽IU+e&dj~IeOzBNBBޫ iցJqW;E:wc&>)3ҾӜ1_ː@l2222222?NoS΋@d_() Ͼ:LՑ0|dFe|1?Y;UW-Ҹz>z~٨)U0t'~Vʃj~T=;*U]ϕ!VL:P-?{ˡ:}┰c/U:Ҹz~QRt7d )%pUextx=MY}ɸ b%Ow.{R˛DoCo|cݝffKNNFDD222J+H: <#==][_z(! vnwoˠjDA[{%¯],KdI7&HݻYYYpvv[1w\G&N(TH9o)))شi\]]akk[aOq46r2222=sFDx: ں-OHttt7DXz^-FL@F@F@F@F@mH(N-d&nf 333Q~Pnii ѡΝ;"3I? QMj;r2222Lf%0))IgyS< )*Wn(N_V<k<'1|kR%J !xvD]he 6CXhdKFλ:TzSM +  AәAa L~P5 OtuOG"zΌA}71力5V/i< sMƥU(EuXzd`c=^P OrnRWg2p.'FQr &f׮mZyBTaP 3ڱLz -H뮋.zL\[5tJqf&l}3k(t(%缆o3@Ħsw?Q52Q3O#>K.~pp)dCG?~ma\Mc%1"/݂Kڕcgɫ+:{R,sr.&Ci'F?1Ot^CїzO|#a7wlLiPSIZPAU^P C ƤfŅz? ̹v/9I7q9h5À^aH8f']ǦrӦOE gKh2zDqE5-N;q?o[pw0$w?l@tR4l<1m4^EwS[O!+/Dž{ /eS7{UkX)u8s9 ť<᭷Ƹ~*\bcry:-,K "u!ؼ2ںN"ۇt@Y?7bpwk~ѡ٥e443/s`0rH;K9}sGu㻟@ncKOXr 1k+Sl=F:xe޵a9}&6i;eL-mimci[DiBff- _}yf}Of /{RXX(lJ~AY bJRtK=x&$&& k׮V^-qqq롚'*ti>9~~۶m… 7(BN{j ¬V oF,x JT/k+v*)0yOf mM,{  $PQP,lxU?BzX8=kkQ>Zpk$ g/\J}Ue% @  8¤aO;'n_#Q+<.1^p37/;gm!,|C [ּ+; |EKNmx_mRxgV!7"C[.o,fq0sh'PG"~#+a‹ ̊gk#6x0o{AW[">(Ls>bsfg,M*n4/`bj* ֝ AiZϝ;'ZJʪ} ]'?עVib#xOXOu bL)4k>Hv$T燋n S*Y[EEJa]/aoPP(" ͟(XZx\e Z'nL߈w[’v ʻe6 C m{wWFƨMذ몠,*J2ߙ,؛hӷ}ڶ*8r./30aQ` tp+z6ª9s7oޤr[z&`Ԩ AY0 7A7IT.\׸'d wbN <=D|4utɯ .w Q]|]Ua=]-'¬ 4۪$?ZW`c.qos̙33gaaa"x2nfffffg. E/SeLCtqY)p)I~ cȘ>yl-d޾%^'cˋ S<%l\[@΀8Vٸ W#SLebeU>P~ 5vڅkmsw<(5']3ҷ]KBCn~vF2Ғ*ae#c/͒rZDD&%SJA,dfF@&p!< m:bCM ) /s̓.i Kt`3:_kk#݀_AO^o~q:B?oE$qroP (& ؊w($Q-PL/mCg^/ l[58k(bp$06clR:qG3+;ٹls.;9i}U\6걵7|43q/,.GcHN`mk`e{_ҏsKGBzv^y)-P<e e1Wv@S?iwc ixu!^Ԥ+ERcf =yO\BOsI;Gj,|cgk_mCr1k C[#Y-\ϡz[~ Z'~’'Q__(uv #pĥ-un= -(`.^5Ks+;Ğ-J5StuLc =8oT>r ],[ \lt+~ 9Ԇ*)F ] n>1a7ìof!Fa[A(x(BXo ;]۠$= ]h7s*&ߞ &2'D0;0kd3-bӱ8X^h?} {,wF6kVIQaشq "4< mxl獃sU<",X mر޽;*[\p7jqOOšxgv@٭4Ξh9܄*J!ƻxc1ޤpNf6f.xGƹ pwuvApcRP(q\J)wЛ1g`p"\eHCQDʉ#Ql"'[b<;d8[c(sL%(oewyC>GWECwE@ ߓyxworZ,D[uAL&\"g%!%h4 ߣKꢘVVgkpE(;n)A1vJ䩃fcPn{OSE:_GQ8obmCWA^V~#"ݩ}9W"D*2`E\+{G8}G`M1Bx~8p6vSW`\wB []k]2{  {sO(9=0(d-;~l8Ld\ż%3g?__'3Xug$aieBa '?>mwrK  ƣ'[5HykL,# Xs{= $* %KmѢuVѸ?ڝQRF=b*Rɚeq=qAG"z!v^\Ǔ=мn\݂ l)t%u(f=Q1WOs"68s)hWE-zz([x㉑#ac^@4 R5Omz¯"@#GHS?XZQĕf9QM̂SKuI7Ц |O IDATOmxAp007ۻ[nmںZoN#-,a!6m:hxa`"ѳ.0x1T#BȻ0[Rp_ݒʦ,ݱӹ86b6yx!`Bd_B8=1oTnضwgO!D09bp$,=5t9Zy&PryI%ቂ {D09 =vlW٢,:Bi1T,#D/H`H4t1V)pVEڦ;}F"ыQޫ #uH {G$zŲ6a>| 0@>ySx;4=H[ Nř]4m/rRPKa6kVyI X9{= {0EUxha 7Ql݊A kO/䝌@$wf#F!^mZŠ:f S2ٳkq@uϳݾMtBZZs Hf_ ɢJh"1!WJu\ M"<DZz.9T, .ȧ2 o70ӋAAu kN8NPص8t逼PW%9]Y@S?>4A_B4iA/ -v~Ydx!?"BEg*ί2Ra2xF:KN ں0"-(711q(tL2[b?A(0nOp1=!=H 5_oӮ3]LpL7rYʕH-zt V9PCJbOF9p$L?~`̙V~_ۗEŃ2h22 }mK{+7Wgv$SR`n60F )+J'N-8RJ\zw{/Qqܽ9YHZZP+EďMd\m*`cG. ؅ۣ$F@^v:mo?j㠙JSq"8J;:%O%&vhiWI"J^lyǻwiW*„ ЩS'c+1*eSYTo1f>? ȗ@\dL콠MfY]+jCdF^GFFz?eZ|nR$VVn܁449z'ZI!dQpi>˪D%Tj0q4"+1+M+>ᅣ;9y$̍*H?x2i>?:A_Rb,SRTWyI@*-`E"Wi* [L%Qb4 rmL.c/}2X:bkineTkTyd~>| ;]8G6{yXWj,͘aP:߱M?+ t4/HKXU A%|P)[SYu׷IGz~1$_J7A6,r{X'Cbxc$6/p 6-Z?Ғ@Ӽ7n#=89[sʺ N 88Te<;ٱ.mW-zMZ7F%>ԪnW㹲*4B5F['F/񺂺$4ix m\@k#$Ų[|d9)UCמa:TKUm'[*E] MZm;b_p] 90|8w:Y/?M(-H?Ĺ/{fMnhcũs1kӡ^=k0"ޘ^#t a_|*%B(' ۯ3LiȊ:9ac}ZߡDuXS9|ʵBtɲz:i'Mx{`՚8k!47KI 95b,{v\ 8 wZnKAi>7/;'6g8~ZL AEpn$P&䈞TS@bX׳AYdƚTR\́tr!RG"u5ROF*m.Qc5Oj+ K= en„giM;_AudQOG\L"tA!ᫀtiXVb6I=#Bq.,Z,V v|lޥTlٱ8*b`ۊ$yjU@{R_hpOaaAwvR2rra:Jv+>QwT"ZSAVЮ~ md}U`6l."BN1e'd0H$ݣrq}C"$pE,`M.`=0\;\9틛hfrCσVse="kJGUҜң lqY䧽[xVҵif C=SzprJs c vV+ya\$}zKwDG [S6\O^"/{YAW5 [)1A#HxTJ7ƶ8UVX1[/_ ҙ`dYInRq j LoߙD&gY8ev%0KwPÖq*<E_YA:vn/]DV Lhݶ*OvSr`y,q=RaM.K3oh{ᥑ^)&Y@$uD>5*zfhիu8 }GGRHtZ,+K]@:P,l1͜*fG>?$jٷuEK^i '=C,VEljGnZ$~m1 "BMݍ6W҂P [M}9.ي/Da K<]Wl^P&⇭'IqVc C' Ntjy oS;f1!pyVLrhSr-htV scu-a )*G_x8ZBU;/hl~$4Whcň%ޜsV/'݋֦)ת@.>=*+AZ\t0@u >0rXZ׼Kº%4he kr4lb/ͮ*hT$C,;zif</{YC'^tn-^D+ ߨ IMlHFw0*vY<19.Tu$9dϋk nݲ$Aͩ'FiڤA!'=X jz48t c'71  _X#:1^Npq?ZcH }#;t%@#S ׮ yLQT&b)tuKhSH/tye[kLhB 4E1 !9J ]LzysX3:NbZRSզA :ߎ=pbKcOFPuI%uBz6仾c/|ʼt["1|E:hq3f([֢w0 -m=Hc~fM9 lFѝvb+l܊J޽X|.M(mg+q=qy4X\WN,]{T? `İv\}W{+x#_{j4scW;zcX8{;f-9?E[|M\.ł5OqD v2ZF[J+G9wMr _6 u¾CbLc[f59iχ xG"3ʱ]1>#5)xrJv$V={("888iȈ>>3ǟ7y+\xLNG>3QMȬMu5{4O|\p?hW׽]H)%..y嚰>)֕d + %58ړHM61Nx8/!t5V3;w4p:[0F#Cnr\R+A: YBiN_Jn:43Zk~uED쒘ۤkbs᫳H 1 ̈́BM/9e# ڸbٚԴ,蛳IYa6ڍ8khã]'rcP7&~tz\Μ9ۙ3mԹcmlP*vJ8~isnmЯoQ_$TJwRt2w% 0?>&O 2Uj1ȠuNdMABApIdo֭ۡS7")feL%e(ZfN#7-!Wuz6 #V,`LJmR^qh"emw$O[jB(.Did?G60vO^p2c^Lg:\хi@9+)'&3z]ظB=aoNH\ṙI7PNʑn- H2xuq:OS NէZ;ό~8AGXvNpu@I~N=BѓofM~ 3d%b4 ]h.6;y(,j1XHJpg"1δT15O˥@ZW'{}W-$$Kh_|Q!}}}3O#^Sͥ}K Ђ']IS= =k3 #y6} 1i,&O}wΘ=CkkkxyyohhP26[;}*;Ԕ6/{Kg7&>u}MemN.ǩ+}j$gA:e&ƣAMRU&r~$Y=Vwf)iybx 9{ľ-p#;|5zw&WűgˤIo}ڳGDeA/1MTR 758"I-. WEdOq'a⁴k-71+æ!}݃Z/( 7oķgj̈X'ґU3jMO>fr(`vRó}GWZPǓ^DnONFl!@يdNurG7j׬{dRdT>w2ҞcpǙaHvۄw{4gSJ`_0(I48r럽#@S+ě}a*`"3gJ *갤uZ>Xڹl zLcP%O]&c՚E4QF@|'PՄN=sF@ m8=Yy4Kf%7&R.qLLsg\Fi&AF@F#ѲFbͲ꒖5%?|Nܩ?Ci+>)ϞY;? KX D&\e9hR]]2}J/P#Rxx4Z"Ky͞ yxQfJ\V&z\WЀ IDAT9<.(%de#l;5{ҳ6>K*ͥr9ddddd&'{.=4SfBeyRRRS`Y!3{I|啃ㆀRM"zd q;> ycrgif/ Rvedddd=(+8,m 4&-.u l<(ʳ *## # # #Мhrֽ2(Ϟ -Z';) KehJ1K3{6ofq )Cd݁(X̞d/ ='t= ϬVr bcc; " {i|tRt}.IVOt@" SE.i|ީzR#P-,,DqŒ >\U[?k7.]$]ϒVI3{ ]j2*8}94 ~F8"v;6KSA9biic-/^|)2ʨ\Dg6Vg}F+u(ynoo={N|D"{.4R&zڵk2e %SCygڛ4e]pfg)w(b_~NM$\YH+w^&9 &T}w6Rɞ3 2ѳ'=Ikkk; r$Rl|Ui[Jrɞe不 d^yqg츇gY'EW02f&;rƉ; ƍۯ*..ڝߝɿ rMzw[>';`Lxɜ`ssske4IDk\16W)4ˤ/=7xf~=H ɊcbqM NjwNjsyȃL( IImNlh*~21OtpQ}RK6\"B&@&vn|EEE9etL\_XK{9Nc?,䙨8Mͳ,eԭmN1vڍi0vrJ"y:WTls25'}0Iu7cĜ|K86e͕F\H.DL|RerdDҵe sZct8}~pL^ y Ò eԭ)qziIJJĥxa-aWSN]S\@cPlSmʡf3'虓;2S*sU\x ߒh]Z`Ql@ҹ|r>!΀J@H])d~NRO #i`$+~ssrhj]jk)}o|$l7's2R^zR7wNH$;D✿DȜ7@KJlhC9='FntTWw7f;\ke!pw=UV9twy9H7c'qWSOdqsD7&ys\S"y-il|?RݤR[TX}ѿ[ˆB M]S= ϕsܕ53#ǟ\2)ow)nL62sP|!|$_AꨈmnDMSy>.1N|0g(ʪ߿B\xQHƠ 7ee|?MA䑚U7)G!cWUNj{2vPlb<tc^xYHMr-I>kH:=z$\h*!Tۡ2}ҔF_t$z~PRt7e)ex|SYu{6{%V'-c(l 7 2~|01_ߥO~1=WD*wmDFF"##C;Ա7N>)GPPŴ>H{Y1rhnw6,9@P|g ʡ~HԪU+8;;AKN`R޽{ {{{qS6>&?vZlI&/5!|NGJJ 6lGGGXZZ6;>,6xѥFU;66V|||||@#غu+ 33OodQmz*}qXvƃ8)%2d?#vF/LLLUCBBi&9rcǎJ#F~o|qƉ M%#G֭X_Ò;ת/!#Z-,,rIoܸ͛7[TWnzDžg={=Ю]; 4H*DEEeqn\ª/И@G9p<&O]d d8m۶uI׺tSSS@,1T? ~X*A)9qDX>_Fw͛7uX''*544srrX%uh IKuSTT$]22222E'̾1|tNL~,^dbuV{؁544p-qF.dv"hAF@F@F@F@F!003)k¨HG'\9R =@x @?L)9Qf4Y|*g wE71A]]~eRu^geVɞIA+.g # # # #`f*R^"zyͶVVVʪG&{ SQ5(!,ՕIA W/# # # #x M6OeJdDʄ3^/(9ao~\N%)>=quEI#ҧbBI3]^CGGZ&\&vDUZNHF@F@F@F@F49K#&O8z=+5ʂZZZ`T߯҂$ /(+*PA2?3r𠈲5 PQ TZ5cQ.k eU2}u/2jg # #P?IɄ(gxXa&\F.+[>ĺ|v^y9  g`ޫ/bwxWr=CYb,,53s*aoe_(éũ릓H.zEKpJWoPRᅒh49Kd)'{ss=gRyj(I_}GqNBvf4NoP0rQ3~g|0noetwQ߉M@QI&@ oӧDQ[dG@bVdtDͳd_L-R gG'jb \r@90q2rU#tfz*adb38y鸕|<<i|ϟ@?\a|wJ* [ş#v^.PsTJ[SP&NpL4X*PU$TNK  >'5uMkS9%+SJҋW3'`3hNI{ȏ!޴Yj$P[SZ4&գz=AU Z4yԢw"Դ5^qVb|^g¯)@@]HrQ3Ų_"C3>ѓMxdT<:4}u{x p?0V's-D]r=KrE#BZ^q;IcecF`\Vm`e 1r܀{O!9C}FFG2RΫDw&ۀ_) 82x'SVT ~HREWoص4J3ql_u)0p=>m_`%)2~!7`Z9{\8pցgd^ul} ^FI7 jnnFl(ZШxZ{\>挃!Y%iDL^PNIP U]k Z$W %\ϊQ`Z60+%|9qy0rޝkz.Q("%T.6} 6(Kr]AeH*"+>~)Bg_T8W|C}l-FkKþHhw>DgށAū"I$`P7/+l=N-PC&Œ ȒM:6͝>tёt0"w\ B``w 6msgQqɧBO’Jrʙ-³C=c3wὯ r︍$B)R|i{o$aժUҥK`!>>^";Os!P߶m{ wFBn@₶كzwF0W(G,ND Dy~6vY<'1@8,֯J%-.xA ,XKSDKÛ?212mmm a՘i-Z$9sF IPq]xeBJNaMB vL} ;#·OtN7k*fKW|"9 YOήD>ZU-(JȖ Yk; :RzfVW UK\~aTzuzPZC6CxQ@fU`/zOS}yaٲeBVVV}K%ul կMBh; zBR!a|N L.88  [”1mBAItfʲp 0F(j‚LL\BJ"TUUp_BQqu87a]6} Oܖ݅u{%,CgnWiMe*wE um 7 kSX%Ǚ={pujE8b`cWfb'x(\(Mޚ:T0Txϸ/s;^%<-tpzU54o!timO]/љs.)M^f"غ^|eSٗq^]' kJhc_6T5fޏC@o̲0QiX Qχcƌ..) KfhjlnprU*M>%9CȞTR ۰f1ѰЫ͖z,>1Sg]'EMP_ ["ŀ[g~;D߮1ky.Tn*ORƞ={pUQrփ]ms86IjVNT-FfVrPN"}8c;Ǣ%/{DiY&2IPT\(b{a~\ Api 3g5$&e(Ԓݧ[ڠ)4rw.i!p5;\Ǐᶪ9?%X,}o$RlR/h^.r T|v"<;cE[_wDv,,_ eXd 3$*8{'ybڬ%agg'6k$m˜.h wo;EѦߤYp *ۯ'#p;% C:.J l[ح#>n~X0H9o6X,&Bٝʵ0Rgw{5v]/S;#5>_} {.&LZ!(fRw>ɫby|8gw__LVpxT]o}U?~H^*߾Wo|x-~8 \\=~552o GRW.|TljZagse;$Q3D70}h}oX{ XBrnxz|밷D}efpPXYiNaՊmps^+# ۄ}%:xjs -J2mJ&pl:5wi8>1vk$|,lWt5{&ºȰ6i''iThsƍC=`kk{JP]'NáD{\C:!1`+ $!FM"#S8;s#\Ͽ3'Ņg`]z+?"+A'T}Z9f>|+yJiOMtFG#99q4팡Z`pR)ybO)TwRl| /?HjM4[iԒL8'5Fp ҋ0_tXu[QVbe[X:Ν!7 -32a?U~0{ѭKwXhFX Bm2GChbĴA]=Dglh>/C-t(&P,XϾ$L1981\ =I&``Od_BGcwT̉\:[qB LፗǑBZRo: gBD(ފ^zkЎˮ5.R+ʉVpD4*ܼa_k cXcvCo:XVlm;KǐIydD爪N; +]2-5D)\c^[`b,OFd\fLJ5jO hEbK O`Ҙ'jg1c. ytI4s%pk3Z- 7axOp-ԥ49DA^JOOq|1, t  \Mr Q/'+Bj:3kJɳtG. bPA6alP!ᗵ)3ڶu+~%9]P`jy̶tZhc;Zh boD"~:u(L%HIM'Dd}=2 $jS\ZfaTzLPJs錱,&Za”i5&זUz h?e8X֟4"V:G靼C/_DzQ!zvSҪV˳硰8)I  k"qlFDƒțowR(@J^6ѬPHyO$3rr\ZiH(wVIDMۈ+F)i$< hO!'0W]|z064e~=OOƳ§S[1y kWR_}g#aLyf2zw JHo|rt=q }{#@8p(ӑ mz4SZ6+gufKd_}L^1'dWh_:9s?K.9p{?BVHʲ1cTo1ߑ(d@f$ɿb}H4,ZR}l%Ծ Tne xwq$cnۯb0ni]8m<=Subsoa?|lu7oNw K/ "`yr6^1拼 |G=;vn.7hVgm1L:ilZѥ%nd]G%-ē7k!VYɎg!}M7[ZZO>4lѣG޾}5EW9s(J%3bti֮ q ZO.m]D&>אϯIXtRj+\k>CP$<[HOҰyXiRT ѩ{_XM ¥XP0\,J PB&n>H)ƶ4-yOt6鄥E23|>g:FK}oh@|~95;s~obZ] ~v6J {g`_h4Vp5K+V]k}*{#EyY=sCwrJ{aQx/ **dZ0TXOIKԘ4\E'A`W,[~A@f8}3 ~5L`=MV F!2,$#i8v&Nd#mk5\5_1xW#+D9&N>OynV3^1bQ4Nk]ԡ54MDkjdgBZ vR{NK -iiٝ71a8i<_^"Cb{2Z_A^-WxT!imisX}<[T{jaY * {'O!3-&I0s3yl^kviܞUk% i-3j6'[g 8?HDnp0ϯڋdm3?-^AAEpn/ؖHk䈬jX JhL%/FkiK40ooEk(C:Ѻw힯N#FjJ5R;E;k2܄1y J;_A ni3.؏0ըSGtiXVb6I=#Bq.,|=!v|lޥTlٱޞegmů3w0{,1Y9즷zsd%uJ(dQ{1,>4)96!bq ZSgOPt IDk鉚連ϘO5}c8kkػ}#Hũ r3 ZyNo`uIS-4p%e)4Rlpw-p9ZۑC|ﭰHT`pgh=B{E0з=ң_v"%[$wޤVa D (&zpV<PD4Is7I.[T4kH!ͣMxO>nbRIE}t_)G ̱--i]^KNds.GtY 4b{ڻ?^wuFI&ҲV1S`6D{&ZycʜOsC_\j'C*Ҫz è93G\ iRX8W{hA=;̔(1+#f_uS=Z_DQ5qԆc&;t0 ',#dVjwУN}8OiD ;7$~η!|5ȏsGs[K\'rJ#C+rlL|-B݂K8+qqqtDd2E%:iNT 8GGtNkm8Ġõ Hϭ qXb&: w=B@(ǑC:>uЈ'=;@<KWEl*a]ivtNa3{.+ % %ԗ‘-Cdn m=p,/ۇ,Z"ODgrz|,ĉK׫. !mF%Nf1QX}or-O1{V scE"|f6*5QHy_GYRˀ"M:#|ït2S#rόXz9[>h\_Z7D< fƚwh[^Hߓq.tOv[XSQW~Px:W-=x89U!ܙg/\!!6Z #'/<17b~#LLJq 4+ ؅)CF/H9X=!t)\w<NJH|uuBZ %mӑx 2fx/૏?G(;hM6NrW4ك{'ı[⯭{i7|xrLum@5~l?^z'?I\sfGK ST:#a5k&>;zضi5Bb0~j޸}OҨ(Sė( 4KVijա=,մV ׮Z-΍5ptjC!"Ȋ ~XFvsD]Tg;4aϯ Q 0l}4;f Ɗx*x!>sۿt<~aOuK;g3n$?wl>JҏhcOb|3Md~|zѹ8|>V*$Q#XR(݃U 7Ӡk.'⹱e HB =oSk"͐yzOPÀþ9(UжS щpr#>GBBJlӣ:u}r:F55m;vY ږe2q%xeWo~W%2(rRQwtARbR2*8*-0y-7+7a/kt|4xXK[QFUMb0֟$H{!34ϝ'*ңƾ7LiiZzirJDU{Jc(w\k?;A+>#He=o&SA<~;vl*J:k)_໵)Ckg|b54/ܷtn/'M UJi{>+߂5)Sٟf/ߌ>J޽X|.+{J懟"ı~2@P LOpҵGЩ F o)t!?o=^ew\ި[[|b8R+O-`bSW׷cAwWKj cԈ?bbW߶geΥ/R0Ur RЃFuCbL#[f59iχUt(\bꬹUT@`/%>_6OЛ)֕">!V*taeA\\3Ovlk^iRZDފɌ3;w ubRZ[wJ[E0! 7\ :G6a(4҆^ۺ t `I ;S߯^tLMb{P Y)ɸ ]Z1'PAdU %Ĩ+lzY=еK)ff6mHQTi7g-]}l_ 5khã]'rcHf7nbMbCITb3g"v&Fu؆zOUe/)Wpi\K$q3y+wk~}{̐f9OB!Ooh*d"ddKZ`h |L<6@3hhH-Ј)ʲl=k7CЩrSbqrmc.]JPlHHJ͢-!ёڐ9AN0&6)8w^42nIʞ;~Ai^R@&FN Τ(dy^h`t9Ȩʁ> P'g%E|;Oƥ {s$aE_E ṙHSI7PNʑn- H2GOѺ8'j5i^}z5)JϨ8AɬQ&JQr }3km I&+!ahB,ɫqB.&J/?Cbx?,+'Ȥr`ia[\ꮤ)Ȟ ɞ5ԙɹHly6}(Csش~ <| &'˻_ʙ=Cxy33>TM0N!ʠ-k/xm]:_6k.m9DTUоMb+NנA`'֪Z(x,$*c7DӄHꢑ .M nM\W膘U1H bh7,#BBCXRZs^7=s=̝9sΙ>g9e2c+NtC[q5k%Z[Ϝ|D36W8BumKԯV/^euc+c7tB?Q}jONsu:j^i 񷯅w[g_/WVzl9nzٻґT붏oUUju_w.k"T/^L8/3C7~z+. ᾔτQ³՞[&ӫT?zqI 3;Y=#A;k0tUG("/af fsϿXmzxcw—{N^l,Q|:2DIDAT1A#·7SfzET|oK}}/|8eGxrY"Ć*ǷVs]~ E`= N.<&+HUϱ#*׶p'uZjHO*ѵݻšm ߅-=9?:|^Mm^f l ٹәY^dnrCH8LC·cP6`&06?lu >em63ϠשF)!E;=3fE?_gs&PprSB3<Fs@YXJfOIOKyf}cJ؇f|p000Rd{'H~֭ei |=:Pm Au(Hyǎ޾} Gw "ldd`&`&`ȢnzFDJv.x1nw)^)훀 @="oXѿsN-m:0L]؁sy{ ףaz00i&к뺷Fˈ(gFFknݪwKes&`&`&&^!#wk״DRnk>4ܜǨ F #RRF+WT'._\ݸq^7N.k&`&`d{>s]߿~zu…C]x@ԩanAB+Vg)M w XG%mAOF4>ڵvk2ikBgV.]]ϙn6 k\XRzLAd<ۈ/`'7; >{nR~6VVa6xS;3gT'OK^D۰e :3 Ǐ/0?&k9xi"'}yMOJ:fjAh.;X>Ӱv_VP#`h-wm1B+$tJ4c08 -@bKlSZS7)bOk{=,#V`c^zD<6QOf)f1c{WStC6Uq|[Pr7t0[7f5N$"?ڈ,ޘH"]s>uJF Ĺ)g>˱<;Sfu"F4N(H0(Jq|7B|?n !3{!R|cZ3Q=HC|,D0UY b8૸(O`&`&0X cUҚXkr-7>o Ao|,h3,Cf!=JòKFT bO9:d;Bfcs`0( xqQuo;z Nǂ/_;.DXXG8b/!)}D^BZ&Th+xqPio,`&`&@,&1W9:).X >Q>>L.3GB'8s^:$ܱ)R-tԿ&`&`& ]R&ӊMl|ћX%ѓzK* b/83Mi}R Lug:UCqT;hLL0ҥhZq_+HCݣ It`D=h 0yb(ʧRu?k&`GL]e](J5&Ո^ev720i5#>Lv&w3MͫW HbtbFpGbR]V*f م:*$] D]) \]P@)]J70Lx, #A5<)A[( }^K4DxEVj RȠybΚ 4oLL?ʓ)W+T(TZ PA_tBΘ @]Ztib/zNMLLA@ݻa400X3`pl8IENDB`bitcask-2.1.0/doc/file_entry_text.png0000644000232200023220000013747613655023466020213 0ustar debalancedebalancePNG  IHDRXq1 CiCCPICC profilexڝSwX>eVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/9%bKGD pHYs  tIME , IDATx]wTڣ㣈(ņbQ]#5 h$FF QQT0aAĨ^9?vᄍ3sツݹ3g9)gΜCB"ff!"OCW'g-} ďnVy}#dF~6DTNs>"|D#GX|3u{@ sNf~"|Dq[^jTV6&VjlLCZfbP>+LJ!V@mQo?*sSD= TLKt<4E#Ga(L!4GU}"|DHוvו-2;myoaiZi2Ϻv01nS󕝭;Sm7@Ň8@tue6FGH]~ȶ7RX%`C ] &5 v5?KΖQ̟j:*Y>ȅ4zhH9OyK/<_v}$c6)8wt;>NP<@<{f,>by:m1mgrd11!}8jYWz\`8.ûW X#; A>C?vj4MEE֮]KT ]tQ V, >324tV6mhd`h3#,z&,59[l}]OhҥnwDbMsiny޸?`>ě;qPkr} ywb5Ö`` oi7 ^̬?X<@Lv;gG\-= 1~Ͻ6򬩏ѻ_nSG]ޝJv,YSK-98Cm獢.ZtҒWO|;u[h8GGIlѣh f⧞z {ƮCbw4{muV*mѕO\Nsvꚭו^)#tW>UzI9_Ζ'iB`ٲe2e (**B׮]O>8sзo_`ڞA H$gѩcvL 9t 7R)l(m2P?\J-[b;NYBXᝏKqz'_oL[_V\F /ƸGoGauoňKpm+IфO6a}Z>^}ዚ q=z|O87xBL}~_zss;iixsxev t|!qsWGDn@*BMMMf9x}=wOz 9n\x,Q(<t@ |b{N { 2Qv~ ]]'Qt'L,O]C}׵CߢWat;_ C:?O܅cC.\NW'/ֿu?u>tOaM;=rl9:뺈bx7pm!H(>؊2 'ߡb1b1ff0v_2g\wcMauLo&^?} 7yKKKiEh9xh0gbnپ>?zx輞r(>0t|>ZjjM<{Сm*jhK 5:qrX 􇸤 pɻbߧyܢ6H獡o;+Gym pBR[E /fH$裏⥗^±SN9}E6m]V];k' N / KRmR'h⏯ =[j?~1aVڒǮ WLr:L|0\S<`67LTYGh|7%yxqŧqw$3h*X0'y ?23W ('Fw+ŝhܡ. Nmߋy3_rӭbHl*w ģ3uU._Ktzĸb+ga/~k]_8wgQ!])ܭ7w6_|{bq9C,dۄS_x-∞ݹh6>D:z-sC4àG?Y{>X9[nPr~Ror ŊYNP诖R9\ p=_~򱪉{ƶDnQ?W`okӖ%$7 LxXK濇<0v|>JX 8#9Hm zP?w}UG>hAN:*| 8ਓp}s^yS- /N6~Zdؼ{<0 þGBjR7w8- _'WsNEwgOZ4s,\S>pw>݀g<f~]DYqEU/Eq_gy,X;B-N8&**@@SGBš):Ŵ1"HओNkHSNrS'[UTThxM Ѫ@x:> XTB8^$tdv ub1'wQ D; Ŕbce}_y fɩX:nV/ЭEVo{M)xm' !Y>8xM7|?#;<B(af.P &k2S m Vf]rMLTwdԺP9J!;ߦ>9 5 l'kfC8g@vʴk6nфH;4z l =,G4-Bw~$PBduaN/!_͋_Imwws;k>034oWʣ01%kq  0f0&0; |tr7eJR>F:H4>su=T߲j`>9L A7uN9md*g{zoT#ݺ2UfAEv߮bx"#?#?ؕy[gRUs*KќAWFc͗٦kC :M*AUCйzd7 =_yTEkFC"|D>L>cŇ#3_ AcM:XQЊrźE\RJࢾrN1pvPՄҦ6 -sR{xdぉo!sߑ0<!SGg<z<Բ.&6ʙޱTպ4B:L 4it.cWoCNE2mz\`K^P:NA"|D#G}AJkÚe% ˊ5IPɬifG/lQW U5xYiyhImWa3??n2G>"|D1ͤ&~ce*5lm t snW>"|DhpVԊBurhC54:?}/k>#G>"|)sr [G3]uj֢7r&:n|0m,m#m7 /'Š~Lh'>"|D#,>D>۪ ;D'4|}SrEG.@@FnXut7Fq5cT(Q$^dCפEd Wlֺ򐙭~RA/t2ҬD7Ve>"|D#_|3RQS9d:0*z E;Y5zrC;_;r9|hn%4Q(fVF ܒ1 Ч(+@nZ~kLB#G>"|Go%L謹)-te:鮞Zj9]ݠla͌a3{;!L?e8م#_×te #G>lp$+NlylڂNÖ,4AgIi_Xޙ 4@ SF7S[,I\>"|DC rY!\S)lC $1*Ij?LnOP?m@'G>"|Dg^d UauIt:r1hjz;)*|aϺ$6ˠ`59¾K:ja 8vs5)fX E:7&'E DwW\M ! I3G#Gy#$۰5^UJ]&Q'k}WLvaG&nENVenŭħ8As$j隴4|1d9'5.U>"|D#G> !,y'f3y.ek%nxToC+1`N΋.C2Q5lr_M#G>"|‡6UN&(Cz,7B:hJ>'0oC?%A~nWOwx`[}RXu $G>"|D‡]vT9eLW!2+n}J\(Ȱ]o?u4gcy{9tDo>;G+]l&_##ٮ2Ctd#G>"|b6C`ӚiC+&[ØFeSΟ`5sۭn CCC@"|'f80R7`nW#|D[ NUΩ"U8T;A.Kf3+m@CZ./| }2e砿kzl̯rx&V\#[[{9'6e(ú*=Q|0Ífg>#?~pt牚lHWA+S ̿[sJ9Uܞ4( VJ_,X{nlEzY!`& +t_DE3p䜡kdIMq=|L"| qO˯Z8L~yh|g5pmfr"|DhY#.OzΫɫLYExj&nYP=˴K,H((E`|@L@#Ѩ9,9|C.j(Vn@Y, &{M@VWk)\82+9g5v[0r{yTa=xn)Gh揟qeVVtά ,hLTgU32uvtϖ\꘩9ϚC=gP5?bsTi `Jtf]{/'N1^I>C&ߙѡTacCݨ1#j͡ B܌;y D1xe r98bN\s(]~U<ϱjc%iDm:lڂŏ=HgNf-ZHtMgSW!mzg>O({.l?tlK!J~zgyx}(nT=78&?v/w/ei^1ϱ@Eԃh2=0_W>oh{JHm| !HǛ>#?"4Օp[ L;05䳍 Ͳ*>@L;-|1rH|ڵ+4m81c m6逅oO^%3'c~b_UܯBT ">|8ڵku>D~!|I >vXW7lĆ skG˫yO{9R?*+av[q'+R`&0 g5C5 ͉swc q@1[մB$%tNHJQ>%jx]9mΩ5}q3bB%N]v687k Z iGcsL> j*kncWϛṹ q\`i@.Pc^ZWoF_aBϸ7-@u[ '؝JLyq1SY5H& 5|ZzѾ}{+">#?2VM:Id&2!k#;[yf֕B5A5W#4·ھV7SLHk:^3ƒxh⫉_GAAr%y#dFG#[wa'ĘчחO@k#lNc量ปg ]/a݋1C;W 7 n]]&)W > ן>eg`LK[&ahc== tݱX[M=Pد ӦǧΦoE=ڀH1>onwL|]vn}zx>{v3ZX̳7K|!Pth[G4DGs?\nbmL+06]QUtTo&94hl3c-oSP鎉0X.DZz I|p&]#\)o /J;{" {:Z3ݺu-#?Vkk \Nt;o֔eàe(D&J_N^%gxMY៕7:ޙ64+scTWoS]4X^׆F0D&|ê.z SW9m,6h9U\>s4]㴝$+>S ݊SC=gb[vG:xW$++7Ӯ@A2usCausÇꋠ_47Jtramqpl Y[P0@ jV^/)ɧo"|[{2>#Gs?rrzCIhh̻et5UDQ`sKuytW~~jR7 K2r`;5.Q$|QK 5qRvݯO7 1_|y4srQy!snkqrB9 Ÿ/ʗH$sR鑕+K44YMlQJP4P tt> C/\R0_9]b)-4;K6-/PU.MT^xkBuLRdSlP|h#G^P2 ᣹qӪMwv:] s}d 4N,()R`wav }5s \-^C}ZaMW}V#|D">#Gs?/j#Chh1a4p2rۂlg)Tk6Vͼ Y=O J`ID<.ӥQ>g憣2J9Y蜠v~>Ƈ悏h\ǫpk@23t홸*Jm&쾉i7a骱z>,MUJZ9L ӎ0Xkӡ#,>t憏h\pA@-ZY8sO&"Vԛj+gSJ9M> Z1k?mq=XI=@&sMlA&󳦌,/+gU֊nZt:bYo:>~` G?ñVA~հmoSPCfr$ aV?(^]bza-/{>N5hDC?#Gm 9t6k&cP19~w#Ay(QUvĦ:fgN! G=#/9C{Dh2 4oռp4$Y||m:zȷ0r1FG#鑩L~F#G#ʍ V!7F ;G 11 @hjG_Lv5`U.ȄC#\jY^`kS :Y,4`1K3I^a!/G->[uD9m᣹,h 򘬹JJ Vt%e:r޷<#=ޜ>9ʤZS,twW]ҘI&AgZFgod3eSLhT4 l9}S`|ht,GP "_7oիWӲe((tompոJ8lg4 E#X.N1g)lmSŢi3(>"|7ni<nFoO=ٟ>c+]}#-0QcMWC5bbYka=UW[ _zAYMQmàx9ojvMZ'lV"aGa7Aiii]vŰam᣹@2A bzi껶k+A]Ö=&|ra G)gu>4>%G'Ja=D>} ^zvG4Dh0eeS!Key,ӡWyXnC)D})}|ujnuSǩ|W&)|0lMN;jM9Ņ湯=r_9zEy4>9F>'q饗fi XlG4DhG;/'zsI{gsc<_i5Ej;dO BaCFkΰ}ԛscigO|ԴA$gDMEW9YYxnndY%oWC|Ap#$"Bii)?p#?"|4#fNE5 ږZO>fÆ -! g[a[Axk3eFnaFGӷڧ|~>L#|DڵQVVfhxΫ&JS|(nXE;4ElXFչBAUCih |l˟?->ƍ"Bb[G4DhNG\בy']B>y¾X; Ȱۿ0M>N~ĻE} 3y>>"|Bٿmmc[?fKZ=_mml(qL0eC[4B_KKX=EN-G|!fhcijR!hWΣBUPԁH¶Ka14f0|FF/5mt(aiA~rC»&rbQwǪq1G=s*%?~ ro9vr }RxAGDD2eM,l&hqzFݡCZ.-z'ڡ.\͎qO-Sy!tl+fF,.\3S<*\M;\g]%!A,mT dz !nÇQ^\y_?L~H^x)3k(ccXsxiz,X&qY J@Effd@lڴ ;sze6~6u]ld200#(dр]o%:BեFB H HЖ-[1Vc!7.aڵXbZn<fqVPQQsSFlzJ7\cL&ѡC5q֭ݽ{wj +V׳Hٳ'b~ԀѪU+t :ЩS'o6lʕ+Awb\6m!H{%K LBRt ը=u:+4W03uHR9 67ȾqX %%%`, HdUUU7c -Zz 歲2[waa!ZwII qcٍF<v]:H$P[[jnb1$Imq0'DD8 ".--EAA9#/R)pB 4?WUUl36[oaa!}Wh߾=:t09?oX~2o,Æ zj555˱5pH}}cfN$4o<뮐,(|s D}QVVd2BP˖-}JP]]qavIO۷Gy5\C .T*E~8_yشi`fTVV⪫; |~())?~}QQXXZ&L@uu5N\וٲBQ޻9uꖏ||ht| 2g shv]Ty LYtEq#n:}Q< f$OFRf7]f ǡBG~,\.$%IԠ# $:җ~' TWWcÆ 2[ D"ueM!ޒJ& n:n:j<эJl޼Y75*>I񺮛@p2~Tqx2$IcV38Xu"ԥɱ>INY(""-9Y48mgom>|dsAA=oQJrx<߻ui^ 9{$2[I%թbZ~^yeiN&oٲT8qO6Wռ`9+LGI[*ͣl<湯cȍC P&/>I+ueVnQd\Y}HB]m422AΩ#37GPxJ@,2: 7H'6lC,G&Ň\)өh-+SYY$_wGwDXSSs@v"|lk@TSS z)e՜>>~uhɷ>E|$IR)>m]`i%c[zNL ;Wֹ)v :~Lh ml+~͓Gy%kvYo!27?ʃLuqacÇ>5|d2ɲJOEЄc˂!ͼ43=UWj0;K4Zh$w5jͼݪug곘kLϚK|ijFrL|Soͩ0-rʍO?̮5c:DW7ä9#8|hy#?|5Zj"mRN$4 e_wնdD{7}:Ul5؟>)z2+9%V̑L VnWTDX|n㡒ؓ'6F46>,!$n|t9?|>l P) a͹) kLl;s[(%,kBeX21(!TE yϡSRZD/[||I$s,[S]닮_A|RU-/bLFMĿD|PeX&N/!!Z4l}?m5gU0gkdMwm40̾ZJY/5(V14g3ykwACdtgteb!/ Qagm;>4zHJ9Y JjݷtnG;uթ9qT"M92H Xc O2+ ^$^Um4Q/QDh1 YvY?: V0{sOAHs~C.'Yi|X> >:?S(K7h% g==AѭuU#lCw@z@#XJpA_Cu3塊3d_ SvPLa?w$ _:Q?  ۍ-d5PrZXC-uBYeYmhT|,+t?SUv8%Ю%0JbLJvҲCcQwGbTܥ=gu9x)ɉ4BC8Ha׭m;tDd/>lG7a:4 Umt`m}4pQ'ۆ\RkOp8LA&HU`aH;m2kvWxCt$L}6Vt <2 8ƑG>4};NO:+GAz.MѣgLY IDAT`|xCнl~ 6DG.OݻV65 >ȶq?Р~4'Xw~품l8TɹI Xu`d(,"6#djKkS2J\V܄ 6 =Wݳd|',rMmmaᓿ$Kϲ\|XGCZ('JcsU1t]"V锁\Stş#3W^r ZV@eExGFC :N#rQ&Y+L 1vJ kƇ?C3I/؀M >s6f`{?r[}qaoJ\#TaMCsj"|YKB?ǴPtG& 5{cHZr h( U5jQ)}/P$69 ƗslԢU{(ѩ%z;>DZVђ--RA⣐udI^@O؟=S^0U-)-Cu7q`dݎn#K.3 /&%ȳHZ ь`&&[Ή{Q' ijr, jfћ~pF](o|W,3l1Hw,cagPq[^@į f! Ɲ ғ`d%SV\:|Lɒ}G94h=ұ3CPR)#O3<, $K R#d_)ɿisuqDu~NqL?ޢ(}{obwK%z,oW˱|Sz -hzm6c~/28#+TÁı~a:2-|K_jQ;@9Y%?,{pY@A %1/AN %aC1,+~2UQb$S;Cݾh?,Y(FFd.ڴ-C##sMz&T@n;WWq/7qJHTbHNe=Ѫ(rd&X Pm;Oc+D@ +@y C7tj| TTբShӺ(v ޟK$Pܺ=8;@UʱdJ zXFXl5-;v@`ׅGニVPǞ/ٜov">8G]9g}Y1mc̙3Ź瞫Eff1eE @lFDI#{ b-3?xN|rL]qdw6E۶mm۶\ZVtR&z.$✱OjQSS#YtAbӦM2bE '_"*'kq~-,jKOue|ڨ9`NNZf+?_ Ѻi3lMN7E$K*|kDVY93ѳ|ٓ" _;w)@k>Ǹ:/-1QڶhWڶwޝw,J:(zd^= [rx6a:w!ywUپn7 q1[T q_2;d1"l>-w>g'^^\,"1K gL}ymm`f>X!\m#Mqzꞥ~K$N(K1q3W135sC{p{kxYJ>HkZ,}lyub3T*˟-^5+Mׇ_O<oIKB&Gw:)hW^7b͚5\QQm!{ ,6,xĈ2`ԏy}`OgW<.:! ,-? ,X>IfgX؍reb{R}ʗq@xw'p ˙nr|=?LefyCu ?=ypɩ\w!jעOS3pUӼ(Sd`f|ݙzW33WMCZtKo #+ao\Ew8ef^=Y`H7|tZޥ':'\ut>_2O|(<^{ϼ+[=_`g W'}Y_/nWZt%Nܵ[w.t.T[[`0~) &7|#n0ؙZ9Uuw3/-BqU|sb^ו\Wכ^IV C;09Le|XJ飺V[[]m7Ǻq^msA䋎Oba*;Z."ff[>}̸]W2n"O=eV|ۋ߳bi?.[,8 ×oOӱ\aW@oؗϻ{i\Nـ#xQ3sңi|tE@Αk>fXY`eu[^``W]P j3v=@%wI+XcfSvw~¥ ^K:ksJ:+`U-X5VD)ɛ3-}n^N}Ҝg"@x^XY1~=AE@`A1`Nh!bJ.P:3)tL^cNDNK~'N7c*+ͯeEiON/Zw :Y[.N[&U⦓2 Ĥ̖}o yݾz6s|xk f^-Nأsmo/qjJ2 TޫPGK׹M(~W L/k ֚!@G%k}(;% &VGshmUx|$3xw3>֧|2qei'61  oC+R酩,ق0>5M,M^XUJFz͇K/7`ℽJ3~u9@NjW6 f" `=`5k Aϲ&*Zʗ#Kq%ofsk;ykt_~x_=hS 6=/̂SɬD*SGѮWh\Wo.MΆZliHYʴ`9Mq瞋KO*]:u2\:v(\v\ iUx[Qc30o-@]1$)^Nk?M_Ϸy-i'T ֦g/{x~mU6dO`ڬг;OEi8|WpmcHJ=?zw'߂STu !5RR !MF8/ Zrd'ނ Ƣ t0N[w!dѢxy8mP:|ftgٷ{35<o9wSHp>x}NusA ~0s{~K-ƿpMo膻 .NWUm,ɇ3ONxP`9c0ARDZcG`_|8I4ߟ5 7=?w0g+(7.R?#Pvy t.(d؇ltt@=Pavto+:Wk}NVF&f ttQڕ@yd}ʜi]){QNd-#Qç4Wgu,*6l3rC%X6 ޭ`>63q{^{'  v]F,F~.=hUvLM7Sn'"񫘆YZ:V*ɏ[.(" r҈;玾p]rb1FZfE o_啵p O@//V@(7;~3q{)W 9XtrQ#IE $&FE]YlIK̩rbPiԔ͹Ihuus<0q+COcF`!vG]#h@閁X{xb⛼yzZM_-LݙQ(_ֻ ZW'\lJ *,m߬]jj$ש1 ;8@rذYU\yʙI zn;xfTث+|JSwJ?VւxߞbK΃ܠcJm@4$|Vɨ 6 ʿFǫ_ğ?[o1R8|9o8KB;ڤhwmY'8Y#h,)h8}8SDfڦl=[vTʫ(nNգ6Ձۨ}Mţq cx;30탯SSJ Wvn қ&L#I[]x 7fO<8i<7V\^e qfR9{Ә=iC_H-,E0`Ã`5o59]ùw< L]u~}>O g`NMY[5 ,數[cTtg:ã{fKzH=;"HSP@QAŎ]?ł)"4aD@J~fv&$y<9ry|5'=U(ah+!Yg``?W"3WЀ˓aI9 t`h1~D\H+}7nv.b݅߱4 .^~WOHyc tn.gփEkx<&Xڷy 00PJn>dB)Csа0H,{-h JϾY=kDNW^G).C&"+;WdV$t2F]̅z3ҙSP_A<hN:H~_Bo@(!Z8f(z I:~ ێ(rjڵw?%&gӹ?fZ(/> $l;W9R yF g(hD {WNzrOTשDҳ +S*=_7wʟnrM=\P҆9ᾱwL+^KLw h |{ԤwgԻhجZN8Ь!9 ƍ/GNNp[ 'tTڹvI;wlǶt"(<>]z<uڌGnoCQ6б-knI"Do{' tШX&7i uL1%}Jb:s`5'&>MQ6ĤVёhǎcNڱ};vއ'NЉȽ5M{~ٝIۿ[J).L )"=-1ZȻ%*STvQ~ 6?BK6op'@snWЏ[ ᶞz4z:l4tLlܺJ#,֫N,ƙ7dWL!zp؃;޾u9zN$$ЁyPy+J>>ߘe:~ÇmKV($zaeytD쿚_W'˳;؟{+_~U7@7'.os+VEI[L[xy|]ѬqCV.Xs@`@>dbeӽC{6eoٌtU샚dXzlɢx3]jxsg~租x d+ēv64h7ӾZO.edS{81bXz{_t:f!5.] Fl|)*n>-qq>OS^msZ+gXm l(9I]OU\vl ˑR-vܬy 4o[Dur0~ !\y&2ܰ5{#> ZUፇkn npGͨ{,~Nw`, }K㆘\9ƍt<6flp?ɡB~\Aķf"@ǛoBP0?'*xƫCСusׯ+p^ht1T Jrd2w; @dH@\' Zh͛s֭]6aVmL(E|x ~v|l@>`jqFT9p@; }fxP =`7S1gy>A|qo/I48ܫW7pv|~wf@*+ cڴEuP4(Lti_[3Cv l9R(Z#5/!k֯fQdxl'r ݷW 3L+Ef \{5YpNZzn6+2eo)rZa+eK;7s_W~ǭ+Kq~GNܵkWzU=d9_#lŐZǿ,[N{ۨ}~|0Y䱯t twܔ=8ѨX-BD}T~{Y}!GK<4sp7[n+7f~ j `OgшPY>͈F̜SW23?z+w'ypt  r:#k+_z ¹;n'P5YHP9FWߎgaf[{o;g _C8^unGӥX /ѨY[(ɲDY9//ٔ|rڵ }F4 ĤG'El,E6IY(S؞*[\$IJ>QZMT(aY998}=v= $!<1!x%=es99?9?b/R6lnvK^{\y Nbˮoa Rv`?q2#6X] ]MkUro+10{rJW]gI6AӊJUФI[ԭMkwC ״CPs͚5)D?NO>P6* $xB"! ׮Q,[Yѹ[gno}()VQ=V_ٳgݹYtAPv7V'$$p~^lѕVL'֬b;I].:tfdsid$L i<2PRYh;vu_~p'N&_8qrPFMP7:`H敿cw9X#bPV=i Q6ľs,7A8~,Pz876YNH0k ">VW,C$yزw8zĔuVub蠁8y>mZ\P~t#Nsy҄H^[ S+WPO0zhԮ]'VacEjSx̅#8:#ZZι SvԬFecBgi58~> V[8W\zˆ]NZ uqpո{/Fr{ ¾;[JK_~;E[?[طUiW߮X4_HF|U6<)6~:݊` z#W7#2P~MZoF~ŭ; JF0 H=h0LY)LUPg;7НWEڍ:qQE7Il+ـ Fz65>! $ф4aT;HdӁC).lƀ=U/VZR:>d0z419‡=ϮÐáʀXlFuz4dзݥ(ûT>;>e!'wWY \qe1ڶl+Q52(UYAb4&H,[_=o?.pAy auq \fkp BB+1)=>gFnlK}]:KŇٶܿCň*}|?@>qoP0rPfNFm'Daf#8~\N|\a6xPE'nz(%}6%<Yܥ&mn!QE1{^  XcO]tؙTFEHT|+>H#+m|6 !ogF5/|;OvFKGJp(q9qEGq3d/nF*b']K}\s2{稃=2}AKE9Ma2fbB /A+/ }m k):-f@?EV*zW]g 2m\\K[꾸T۔TW[z>;>=V(6?V+M*BdiO!x pQht^&ezE{B??I;\ {4:%r螅I\#"}OU>Tk$?P} zImZ+^.c> OC{OiCC7A+ |@pRNW&>/>Ll`9+tDk}QfsOK;`}lgս3s}O֢YqV#tcG n>̶@J훮&|T(Gp(g(+XMҬ f,i^*?+ WN RTw]K]dZ|\., dYf%6iX Ok?edYVv̲,{2>h돨EY6`+p U1~4ezn{JfY=oGNNsVVܹÇGV8//L@?E~:@@ExoIɌ=9r111UW*\v T.*HmgXԯ_:V d֍n޽{Q^=hW%>JS>l6*Upx41"f %ӉJ*[neVBrr2dYFӦM]vt9^j9@;w:u`߾}yfHHp  6бc5j@.]UV!55̌-[UVHLLիYe""\Q:#$$DlTd|hujX/\bbI+Wm݆\̛7yyyp:hݺ5:wGJV{ӆ eXVv 0#""hѢEDf͚t- ##?\._{Ծ}{8p˗/f>}P\\]v͆pqlX`.^Ȳ,SѳgO\p ,Pܹ3ZnݻwcժU*TCfuqU:o.$I\[.EEEL2 EHHH!avUPP\tJMMř3g锕<n,uZdXX$D~~>dYd)b NQ;ueI ͆L03;NhKWeZLD6!˲L->"""0w\tޝ 2RTObADD$ILdYqKcdy:=Zv;l6DDD -- . p8rrbk6^=zz;E$rkxDHcjpoױ&perV&"buXS uݖ^w\=!N`6 !!!@LL "..ʕhDFF",, 63zI6BBBHT^A~~"t[0s {CŸ,{W&pB}x!{yC.*:R1.N ]&X+ƭ-[֣g= |#uD2Ƃ=qX"3>"##QBԪUbbb^*ir:0^EW>(Kڰbf"Btt4I9d68~RGp?!!! Edd$ʔ)C f#: , v;fCXX^+RV|y%I| Y03r9=XEye][PE\.Ƈ6R JVWZ/kjժ\\K`^SmCȫzawᣘ}_|B$Kյf1 #0(!8~dQF6 vaaa稨(&y w6 !!!炂R&W-`CQd.ўܧ?98+- ([,BBBPR%TTI4ؖh:,nX=-TosMjm'~uz$&LCODl!‡ƎfnGhh(V$I&Us!RbW8,,L\Ai̕ %ixE 4x ױr|N(.>`K4W| A `U4 :/7K  XW-IŔ:R=ZT*vlCV$I O)Cյ.ghmV*zV+d IDATN'3i#Jf_U*:6KIQZ6Tl6ʖ- *GSzڭgm,d DEEaԩXv-~GdffŲJȦomߡgMX49a@1܇Nj`&VeMPkj +zqh"x1bBm]i\P\숌DtthU[>ϳEV&'[En ^oǏ fFHH~l߾O>Mqqq*%#8~@:Hb!Ō9bt4j,,ˤ"xr g&}\ E H3hayu If>fYVK@րV@3Ɋ$h+=En">ŏn$)>`W>, @~o{1 \eG2_2> )睒$ĉm6І ^(L |h3hhW(^ 5DV~S 'kƧ!Trf0w  3R^r3 u@ɢ]L ^UK;NX%\w(7Վ-f,܆",e|Ap-ӧ{RM0s)ˈDKvf#@<&J={ I頺u[;64jT6Ջxġd;Z71vаRvvn49[^2VcxɎ2hTbyLIƮgPy3{yD9ѹc24Tkntb͚5{wڅcǎN:6ȃUmg0z^#g',:` Ϙe~DWrڛeJ!RN" ?9h8/] t O̅f|iCՊx`/%Q^t]Ŕֳƚ-hiE̞٧N&_kE {}I]& e =܊ѹ}"hXz v3|/M6y/((qma&Fćlڱv(X|{֔E+ 2x+Xf>'Fr^7{;xmw;3~Fցx?}@JP-|i0zV#ܼܺv?Ko>}Sd),Bb!H9p; {v*.$"XM7AX$$ϛD1!0 R]Y> 3gϦa}3[hq߽Ob,ڻ|=/؝9s'Կ7X$Y IZ5=O?MJSp{w/ H2@ʥ c{uǐrxLwluЄ׆bpA A|*>ġG&zɯO=^nJPV3ό@>ril>3&648~hժ̙z =zsbРAA|\a/\Pf+tuz3~mvZu<6^3N̓p 9M{l 78}`5eGZa+[f}j@(*q^^>jG=ϳWFzsWMsvfdd\L) I˰b8/ 6~ +Y-,[s8;"I3{]8y KpSKd`o; oo } zi)βktv1.-܎/0|"C@eځnqQ<_lh'lx(z-v g>X;|5z=Heِup1:7_ӟ/Z}FrfGw-OL:xuh^]{2-8t0iK+R?X= %(Mc$|l}<^huσ٬Ǟdz)< "\.Ԯ]> VZAйsgW>uP3Oc Qbi 3yX@4۝dT}\3m鿓BkOS/h ? OށoMӊGڟa3ԶF^eݸ.>j#X}#cMM H1l.Yg4 ;< KWQmwGGBEM(gLGa܉=M駕bxjjvG 3SZۥCN]FB%Reu[c3}PuFɏ$U S wu=1j֯[ ֔]ӔW~w5x˙~cJ: a8~ZBm^ y"?GYId9xeojIӺm'U@?D,GW.RW?+[qZ<="ґL?KmYXV_̰X,HIIAvv6i QOyZ%;#7= ipiygclT rU@6pdE $lXϭÈ:< Q0(C?If:kW?3L>Ƅ0 YLu?J&?I7OnL? F1?ߧUGT>=^ì|y7k-9G@ E734 '@900j; ޺!6ھ'GϽhZR!#|d,H$(p|M ,VEN!5$jW굿hݪ3bSXg31}{1>6W#[@AV)OX@\H@ Id6.Pʨeq?œօSq";K)1p$'eȆQ߇ޝ< $a$Q0FN\v7,ސJ\A|"> `uPygi@g&YYK`713[ tq)lA|3xIT+9"ޅAM2pb$$9ӧ`Z^Rqe ǩ#3ѡ vnG5/$]]kc'7c۔V|TGFNz.;b,g1t@_L^ I3 zy4i}:^[Q{yKļ:ނcx89@ϤЩo1k]ɐզتxp0XR@J^6n@INWyFwEسWjZތG;w G-h|(i\qK?.=9m22sq1$a3poftő'EEbq-a>>6VdCQZ(wR`1 4-ߠώ.q?Rk9=sobK&ڙ. Bz]6Fi7Dd:EkY=u<`ʙq:!skR1L'OE~22˨P!׈ <瑖tFҍ;px$ #$mߊ*hZB gN#B RNUeíLAZ6RrH9.pvmisW0'<]!P g;Lkեm;rMuAQ@Af]WӉ#Z6!Z%%eIG`Z>5YLb-|*9*W*6sTZB)gNCTi/~kѲ^%c۱]E$ƾXtj\nYC:кS.D2D>te*#62.&AU=ݰ]xoPMu 9iyٟ~]"E4kM "BF,r׵GCoEhٲe2eJW >42P.gu Tl̒ҕ :hjnhZ5{̲$d=iXAJ{Q*zDIܧ\!6=L/~^ 2kkʄoӕGjYYfRQ&X}{Xm^L 4j'XA|&XA|\:>ڛfFy{<b?RqݙE T1WNaN72rVAR&1$|Yjː$MY) ~)qg4eȡYH+\Rg^rHd`Q,I"6{F:^F >Ju:άva4B tAĤnƽcXW6!ıIz;/ 8?m#+ven5k1rl/.g{y Y\5ɉ[ [O xDԐk-Tl)^rWQp]u@EmOxs0CUZG| `Y2pQL|l@Dmt^ԁ] ]FeO#9ifK:fnSQz0 ??! ^J領26U}q m.8~>lB >|Hd" STgYk\k2ry?9Wl K-N6e= ^;r3}6 8N" G2\NW_>^_?(8~> >,|HiO0h>I@QabKF(~3 X^cU6}J~R׼2EA~L$/ (y_Rrf^([ enkz|;7wIډ? oT~|zK[B[zИ?rj!_O{k$Ҍ'z=тE]ҏ;nw}%d;m] IFܓ#o5sQ*+ަڤi4m̽㉩*}׽i==ӟ:'hɔǩYߗ1oxj" #*?}>nA|JU%sׇ\~ZUi ݉'5rL} Re3m@"&8~>>Drqe*p1B$^GIΚi FNBw ht) ij ~qړ- XҺ!RrQ5  ©jylsj5ms_?:)Y"cf]\VgZW yArP5E ~=fŤ:mѳ g/PEot3/򑺷8Emxi ό?m$L_B7p&i_PÊ!~ M8*]FbͬQcݹc k8q`oJ;Ħexɔ))m+W:5}][Kшax5&#Pu{$ G6(>e z:1vQ}}Ы Y?uqkQ+Šm`L;;W%ĀxX=qRs{5'Xckm?.>4 qaTܧ~86 >f'cvERCJ-6ڭp#cG"kxDKy\t &3ǹӑO$:dTy+V uƮjÞ}ܿz3*ڒ!A X!G[ߣIv 7" P\{rF8Ѩ$p0@N_`}uWoDdl5է#pZ85Bޓ۪q5q!=ݝJGyFrR>0 ='uMd,j9n2Co# ÇswBn/n-ҳ:( \Fezu \>hTVwz=FU{mC΂-p.0B^F:>7{ZDUcdϦ\wIv9.3GvPTd(-Mzk ?Ҽ9onhȎ|Jws 稚+?ÅyG@IDATU6jVʩpppR0\ >Ľ.y2 Ts&]G(ϕOɎ=ip壢(ߑ(:t׈F?> = 'm'ʕGrQbIw]3)am:c#p:a #Ne!oD[ ߤOC{:G7$>G1aMDFHoms--6_AنrF ׯQ[Oyt|08~\^|HW(> 3(.ĖHeG7zYI իYwBJz\|B=|7f b=c¤7ЫHXQ\=랎.$#_ɢ\ķh?hY;3Dx >^3wurG7po-?HOKBVJ2ҳ4]jYL<>*~y@rɞECf8$׹YHNmK^}/oҥjXOb`k pg!`Z0Q[Tn9_V!J V?^{w7܄FBlb47o<c~._=c0`:^>wųw܋Q/f}ڽ;^y0wdlIێբl >nHBV3 x lu+*;PN;'pp(9|\΃gA,^pEGF76χ&_`Mi$u3wΖѳG[al8B:1ض85k߁,}VpHnT7*v EwjԦ?=;sƞ`qdb럛QUTN܈<V͟ɋHߍJهp%UD᝶K^Qj&feFmﺾ fMG è-T9@|+CII{flR3,SK yJhgQuԓ4೼``]ev,z(y5]fY̜ 09 U)ӧ\MA2 IԲEŢ+#3{hlǙEBtggvL%jty̐N -yDž.\ ߋ-pVI] $yϲ $Y|畲SϞƝ1A|:s0D:W\k~ڨ(mn*QQIęYa\kئOJQBQ0 m. ><|X^x)?Z ]֢B'"mI8XP"n\bT` UX)9߅Fn]db?Yy5У`U?ڻaP>NT]y"̐,+Ϥspurw=*Y]U0堂"T!Yee2l%jXgaTAS* Za3¤fhDnY˾wν=Sfvv}s9s9if\5$'Vg%r9pv68eFȃ5ԻwFWpeGGća"fءX{XBͼfcJ$7rENMiQ+kE*Q#qi[uG)QPzVnt$Y# Db?jreЕL\k&^(7""B@LLV `:P(Pebtpu;Q>7B1TS#0*q&`u|Ч~Ls8t*cysÃ/ߛ>]}X5{3jqU;qXკ2kk-V͜|Ui\.ϮENJ{qY012~^S'|5 ɏ1CI fٵ\֎k7A q%x䅷qm?k*vy^C0r‹P^FȘx+n 3~;#o鰣!] *ih ooޔձv4j؅^$m>|#-(-!Q|twwc˖-={ ~af$n韽 cCw_.X'>U?S';nvM_WYO#qq ?K_+NY:xFI8YXt.? ^ۉmkobرw~#ϨQuV8~{#o3(-w 7).y=XQXB Oŗ} ׄ!ȗrt@^dYu+ozC~4RMn30%_Oˆ񷝻)0z2&UN~m @twǾc?0. pa+cu8;LL{컧/cMEz6;v5{ > H8U֍ |lEg5sЧ\.>h?1a匨ȱaH%g%6[RxBgz6f~š܁YF"àLVWK\-)2 9ek Iff5m+"{6 -ݧJ ">">(p Hȉ8qCgbѵ ݍB_ަ4c̐7{7; >qD#3Qjg/h= q(a:z@儈~ ,iw@*| 8Oj{ 7$Z9/2#$MJۛk_Jp(/-;KL">">'|r)k U?FҁSW)'!ӔA eJWE|D|#QF+vl+ 2׵V)^ԯ+\' kץ^N oս{2Wv淖ODHgfV2&mAŃR'3xn157`Rț(+^6d2)$k^2j=B%Y}}L`L&4IߛzV[ᯘXvVKG1GV&ʋ<;1E(b?RKqq5{\.a^8k]T-9[-{㞒 ^|rzx {JD|D|D|D|D|D|$LͭV-]B<%)lw]R=j2φ4Ю-z+;m5 jRmIZyQ;=(*IENDB`bitcask-2.1.0/doc/read_path.png0000644000232200023220000027750513655023466016734 0ustar debalancedebalancePNG  IHDR}QiCCPICC ProfilexYgT˲ّ69s9`TD" (xP"ID  *JF zuYkzu 0("HJNr@8gx9_q0G%tb3;f{x{.]ϐ[0}(:"ƨ0Ƈx+F~fH  Q> /R 0#zˈn {Dǿt¿ O  l"렇[0G/|ϟ!|@lAv0yXZ5} ma C"t0 kq~z09\=F4=5Lou1?G(h>o5/o ar09ف XE`7ap$9aN =p`~pQ~0=p/iҿ@\j^c1w^pHl/ OIqq0g6bz-Y5>8y$/h4< upA @G(+|@~ϲ@m`L.(?~p hp$.ˠ p N S`,`l]bX!nHd!eH2![r| (:ҡl*VꇆW}vHD"qE!rMD1C">#֑IdB %H= C#Ӑrd هE"( E@Iqjr@yBQ T! ՍE͡VP?84Z 6A;}t. ݈A #Qc\001L=!fbYbX ` 7#%7,!+YYY.Y }dV^mw)((4()Q$RSQPLS|T$Q&PSަ|L9GMEO%JGu*uTp8A6ufp[ Ԓ&^ԧGiihthܡyNDKN+HGK-m]c ˠH7Oǀdccd8Pð&ct- ~Qё1q $d4δɬ͜\<¼΢͒R2ƲJ`5`gbmf}͆beaföĎgWcdOco`@prrxʱiYŹĥu+>"7&7;'#A@'tVx8xy"yxyvyxxxy_Q)=[?_?)@.,''')($$xVYPPд0NXK8T\FDY_DdH! 'Z$\ !(F+GOHPIHDIJI2IK&I6K.KKJeIIV1Ii&+*)[$B'g(wJEnU^L[K ܕ&ʏU**TUUU#TTIը}TRVTA(Ә$hk^՜"jkҮ#sLβnnIH}#4AzBC^C_Z#FfY&&&&+J'Mͨ ޚY ,L-.YL[ XY6[+KVC`lml؞cs۰׵ϴrvtxHxرqI)iY ɥZ~C ?"t$HQG;h܈nwN5?Vr⺇GNJgg/mEo l>>>}5|/.i-HcJm[_p $ tl   I U fV oÛç‘g"4<Mgw8G'xN$;s,tTϒN;nKLNH~wLm uJXYPHSR Ry=INM|. f*f^tq{FwRuu GMf-6vCoIԕ3է#oGw[И5645϶ >jSkk'yz;O{QcG}=Xpӷ#GS]]/m{zv=x_Ł O)|]JBxYxWOWWVVe|g~}M~ѺFf֍m??D~!pu8ps!()~I @8q 2%ZF` dȳ))#h&Yػ8\ey^>G3^ /lAR%F}9lw1h$bGgm_?qeAMEFducb㢏(>Yuj"Si( KYsfiүfs>tvg9<|炘ⱒRU2r JkVU͸Y|V݃'Gjr㷦6=vcZ^L~e׭#Kۻ7߶ÓuO͞~zvCCM#Q[/NNM<cRbrctk7Yss^}5/60CGҢ'Ϩ3KM_җ|_a_edf6rJBP#" k*/F K"GP)ũqJ1'O ѱp$ss-8xxE1.xA(C8Eh^jWzA]b fJ+*C j5l4e贾k^;``oa$`Lcc2o:hj^aq2VNĞÁȡGr^F,(+$1|`ZPjِaI  QѧbN=s"dL|ܩ Ig.K.;s+Oi;.e_"fG\:}937#/-? |QVqnIɕme_m_Gߠfᯕ|Kμ_> w477mul3ۮ!qCï㏚}{Tz1} <{2H98P0qDuit˱񂉸T'Y'^N.I~=46]|B]_/nf[2àݼ_t!(A Ph4 5#"QPq4%t .TL,XVI6+vD,bBO:o APQ^y*1- ) 52$YAJyˆbҞrJl>[~cҁ4Aςodل+DFtWtUX8'N|<9}5.")gSϺK5JSM_%i.>NAEEcB3L{#]=AX̤4|5=c3Kk#󅷎/_SC0HhX$!&Pyw[EC% eIUQ5Au~ ^M^-^m>]!=}qCe#]c WSYyE'sX; HzEg~7עC/0ur=D   "xI GWN;ß}'2L߳?)XBWRv̵/kucJv7MlcP}@jXӂ;C=#C/FƟ7Y5>0kV|jaEϘ4_#W[Xx9a{uggw؇~ כ`@ T`'YCǠd E5(F32ُ¢LPWhAt,z>!lv̂\B,U$WKmNJSDkLMWO0`4bB10ذ~bkg qprCo =<ռ]\PUHL_ -#.(!)$'m-&,(Pة4ʮy_kYG^~q]_ lJlg ^N.:;|4 ;gOt8lr/L"<#Vz'=N-$$O>i3_ɔ؛}ҷ˩y~Пs pHYs   IDATxxy5|.z;bE]\"ˊKc;O'nG){; @\4r_<ݻev[f ,X8`qp92-X8`q8`,X8`q`Dp]M,XA8`#9*ttMXgg+z?@o<`@x\0yOLeG8h4ϛ[ݦ]L7:gk똏 F5s:z=_}%0@ J + 5AΝBBzS[OWWnV*/W&mgu2 {;x_;o=ͼeavH77O:Y}OFpY-XD hB?(\^LL>LLaL~~~q}}})uAAA>Į[U,;wNI{w?Βhd爵xg&4I {- {WyN9PY?6dFŁq@/xH(TJɟqOL{,1cRRRӟ?ƕ+WGjPD7wݡӠ̾TôL' R&f$CYOdL.MV<gNtwz/|-îEو5q vѲ]5*a%8qƶ;#ܳ[80-PMzGɯ~>X~| ;??gC_0IS\ءlFH|n򜴌#wkVh{\fM%hel5831f7Pn; *{y=Æ[GǺ-~^wrɮw?2Idc}Cp}>S_BqǎʙS['ߴPS$ΐd@ރ! |ZGspIcl19`#ΥK꿮fT7RpZ$7 F_[/OOYw_җ-p6ǝ/r r;H;u}ܽ6] 7$dG}7]+\mg傦6ϭ_/f͊bʒ܇t]4N[$_~X84Hn4^Yr94cF rv|pM҅W1<_LAL LyL LW06x UƯs_;!\m3bK/#/|}nak;*iщ{e^O%O>VU4&,S,8d;3YYTO5RSsz\oU ¹e%"PT? !\E+Iv&݁i˿ ;O`1F{h#0wmb^A,w^%#݅Pg[[Ƅޔ)†343ESxQTڇ((]tUDA] x^&^I1/9UΈvE~z v`v>Rnc}}sw.vdL\!\5,JUt;e"whSDWPq+폖`ުSwF<,` ©mǎEpbg,*ӧss^ z{ J", *Jp]ca# !mLW>nvpac`VȺŁ~p4.˖-mذӟVEя~mc*avYAMX׼'#Q5yT780F0Wի-מ5QX(4]E\l8[2'yh;$’LV"Z"dGw F7J/C[-Phrp@aVV^upH|_4; M8qiz  E֠D,X|&D89׿ٳhWk}mhZwiZ֍ ,L= Z ŁShkʸ|r$&&BO~p5Bzhuh?Ə6W^1k_4} !{Vdn{~?a2oZ80E9]Bkr8y$VX:ܡEL[crmp`sNb:U 6 MpC9=E,Xr+>|v|r"..Az7 Ƶrv-[ S# ~ f&@XۙIXT)'̂Y[nȬH>*'GΝkh%2o L,dh]oTM^PP?0W%''… 'Xt1V hikŁj́'Alo$N+nkFK;pTVV"77׈lš5kxLp9aBD`G }ۊ  [ݻlʢEnLtX'a(I0SWO~SBJJʸ#& @XLZWTtسY8pSq@aJ\ %%%Cvv66oތ^xޒ!i'*4s;g@ EN)Z]ZBph9vkdFLcŁ8 Cu%lCϟ7bM(|(뷶\LL{4 H@ߊ3^@ JIΘ1>u)Pu 4!>.(ez8 a/@A`$R\Sa,ӗFU+HI/G0bRdd2卑<3B:comKςk-O7#mʗ|ke@iCvZc5oyl'@X L& CrX80y9 aI h% em5ד*1~+JIڌDZ`#9.Ja&9/ B2f' mBz>DiLӦM3U󸶓,L~;n' @>PWR-C] h%-hHXL15me:yt JDb2'$G*@B`}znȲq"2xEoZ8`q)V7IT9O],9x0~9Ur8`qŁD*þkqS 4_7:2_90ڵ8`qTs?o/|uj*1*,\xc}\y֞- +r {ENK-y:ڽ9v}jVu6gŁqJ'z.m?k~tڟq0e۔g͑]?;9AD8\&Z_\HKmɜݪ>غuk#G5ױ \uX'ǀ u(+m#C̉8bÙ,kJmwvvo>ظ^9#6݊v{Pq JƵhF(3aeCX;zuha듸r< TG?g@8eM΄)=05ߘj\:O=15d(+pЃlyר}O&_)E,\~GvÍpW_5ulڴ Zqޫ[?{챛@w`ߵ]& DC&bp߈'AD㫴@ h[nX wtMUmn='Gk]>w:͵ŁA8@-D&hpߊ4,r`d՗4|UЗ%W~l}(\VY800 <[ᛕcY߃Uo,L9)P&G6re)D,X90B>dF_]x32٪nDuCHƢCW3]zk];VUVa"i2a"o>{2qn\v`tk-rqHL[k=nps?C9ކnxx2n6Tݝhf\\! 6jލ.c xWsŤ9fNj( ZwXkgV&ӊdZx0ҲZ?YK\NW$4&T|IZL$tII}k5v#ޭػc+l tlxƐ~^޷n1ذ|.=(Xrt5rE 8€xy!D̻nu\ qt$m@B\U~Ec &N4:Ea lFкӧO72<ƺmmm0ޔUUU WQ~ڍڃfCp\u<ˡ2CtR4mr<;೨M67vՠn_k_+Zfp_`tz4wظ^7:[g\ܼ/`ooE}5&w%lYeon^ Du!w=-|E7AA!p mw{ , EP<[:s1"\PUV/ 8$WdKä7̝n+.++3>(a!eDLLUkN5k֐vEҬ ]~H F0{nċ/ߚr veM__ߛR ?~ij]X}'ťc;l} E>y ^5? KGnCžo~I$G\myژGC}qLJ%d$CK %8wi'3 EāWWfmu8|<bAt#vny6"45G?܆.D rz2CXr@zs29.ULm`O69eM هX'"†MYMp ލ3q5O`ڎRTUGg4?QFXs_-RQ lQDkQ9\!QKp>Gvng)L)Ǟ?žnml+@1kK-\]ũڧ-ye,FiMNP @69jW]%@P2}2gfF0_ݫߎ 4X_v\i\yՓT._0L!(G]?BgcQllzjObv*|auZvWo:ޮ 8|z3^(ĩ0zt0fqz,Gvg_=0D"̨-H*:h'uA`P{ 5EXhРӎbgG eńoOFqmd=V IDAT?%HQS uLqcƎN{;ꛨ21Ժ[yܿ1+Mq?YYq Ix`}:0{2$EaN?`ǪTF#> |13 $Lf\ m󽉧x3a֒;Qd?bL\vbZ:-ƜYX,j[|g>n  ƢF:"׉KLRD)&y(ʇm8@;> &#8@dV_ 562Ý|0F4 *pIhGataƴXU7#*.ܾ], #0TE+139]-: ֭BHsT_%YvPeMM3\݆~ szwqkH3 ^:dg> H ?W &:p)>e)ezw5,F{T.Υw(U"Ka-F=/$ob޺ד. "񅌼Z.+C‡uiikJq(.t>]@3pOQ5;;.!/;:mԻN?GCQ)l\˅;.>H%>ÓfB* &GH LދnYwթ[G]u,(e%Rz\u 39gGX/;c w8TzwM!,D]unXvJЃ$) 60H$Vx: q4$,gl:@dck*QTRDA.gH됓{?7u./G/LON@QQXO h{Vay52v豣(EBƮw͈ǗwG2P9;؀ yHHNFWK=ki#+;vZN,]²ٙHI ػ3.DB|\-Y9ȿTp,Yr?qKcEjmSeDK>BP}s'/aVj*kN13uJW7cɢh+Gv^⓱$m2ϝCIY  b^ie=V_*W`ʕHv:y{CduM|sJyJȫ@Hyq!223Oc;XS }Y'PR6NOyp`BDcrFVVTB,smD{pn&2D>(->|?{4! v:u AuMf`A3|5 ܋ǞNډV Ymj@~Q5ǨP]v s2GNGIQ\#C߅@ˏI? ~}֗up pÉGc^l|쓏'N+/GFVp<Ay _hoc QMr`*d_,g|1^|!_3M-9Qd9FTTUe9+EcS;W-5]d;e|VN8w '3sӁ2`w n(|g3}ُPs/ *6 5մwndU71'sͯ?v{嫱nG8@(gq6ݙu15^ZQa.8x/چSg')h_L (ЀB 曆l Hܴ4E&iCKc5o_ {L.[22Hiii =,DI.D&LCdCfn!ܲ#m܄ZK/NN3IQA.NddH&.k%9H_`0nY1Xl f%DshR FSե?΁~^Ԍr{.3,CW/Rb?k mmv*bp:CGhz:?[pomiI0$:O38{+ n׿s"%Hc#յ/ZU/`f\sPtw^P႙I(Ofoc(pbjjǟڹ;jֱNV61;ͮyIAaH]mMH?v T5=\i)F,:x_ˆt=#{e Kvj PcFMʥ6:Nh?GΜK'_Ǹ&Ik3` BdD8\< ,T?_`LP?rp?o 1!曔=ژ0#jh˿HSÇXR;f262gׇx{h9s.!kRXΝ}GShH?'NGNfVE?CAMF=u>&yp,<<?Dp^qSGp%KnAtE`mH "#fbDs!80yI2XD 7QHH <PA3 *X~+q〾euL 9Lu̐e7*)k/~Kng lšR{+&̦:) QYV CtL #4@sm^J0Z9^‰ؤ FKh d~OiO2^V gVyy2 |&r L|C͝| {c-cX{zηÀvt z8\g .> 6{] Gt)Vad9W"2*822k2p`!^kO+ Jbә0ASkBFv&2ϞwH"],gCk|w$! y> $j}U4eRV&\j=?=n…аZ>r\YU;OZO4N'7#(9SͲMl1e z*Ԏ휌讒w#Ye;H[s_!`P8m=Oɕ4U:&k5g^/٦C[ur5fN&r6,sa}Jprb|9]C#h*hdKq~>}.p_8B G1_q myֆNpo:z[}=IhNJf*Nf Bc?}6ݏ-,:!ɳfc%Xl18C06$Θm@uVv`Ü Ch='tb8A^ٿm( OܺumUbJW$/JC%e4F?|J2 Z`|<{%U}:aX8LsUsJͲ۩eUDƛ1"c8Kc6Q{ᇑw(> ?۱atz: ì yjZp\ B9X2|lݼQW.Ƅ}14I?~pw EL\7*2lػ}3Nv82FAm!$/$w55(io%ABuRqfŗb̨!3l 1u(zK駟Ts9 @(սK}?_ՠI DL Ǣ`3ډUX\7_pϟ a<|M@2ND`ɒeK5 RpE3pNxp>,:+!&i?,SP\Vi4`|1Ds}cM h>YT72cPfL=&:V9h#V\x ǓZ9 ;ET+ dF]"1)Qm4BIfD9ś3xᓟX@MQq*{ V,2^!c=/`l>ccy<ЇzU \W aHss"gr"tݲ9=ι|gR@x,J7]I-ˆ8 eW4($VX+x-pICbց\ч2sF<±=Y)G{`k$%#̭)/t#AQT>i 4hɌ>VI`!piB,b2hp6ä)酩Q;=hS`wٲ]μ״+ 5y]u l:E5ݽ6KM0Z!<5i1A=ED {7O ɑj 4F#G")>!+}0\_3(0ʄug˿w3g̞cکɉv[ojJӹR@MdѽtW9~]PMrSj"9 &|*l&`j=DM; 2 j(Vn/o =~CJSO@Cz+[Π1 @ho( (dTf$8ìd*_ܸ;wF WGa<̆zM&&.1.@]:(Mc~ShMR 7o06 ]ΏwhLx3H82>o_2Ї+߻?_%`VRo_eb@B߻q  ?pi"6|5hM}@SЛ3d foC/R@ `$ۢdc -u8In b$EWs@f 0\[ .ҥK9g#oش&8L4G H$G<H3fpZ^3B d9Ln-ux[E j5>9'QjC'd.ۤaMR-L($ȨH&8S)P'Q$" EڃGG9g)#[Œ"up{9뛋ljZUJ"0:k*A=L`ls;,o3d"0Xd:VlGm=PgPI%ƟN[ZSgL"ӧ.'`2P>0<̞={F#Mѣ8rԓНI0Rkf8*CuuQ& #(oj)B#h" yN+oehq:p8z9D0!Cцp kABPh?JՕ#M^dXvY"iXTmU .x|TwgE O)o:OMY᥎"!mB"PzgPuL@BCGۿ\ f9QWp fj2Ngf͆)wΞ5U~s3:e^LAA $Ky3o *#ݬepD}L;0n|`D]%%%V~c\AV@~\XTC+V F|ֽF ɕ\`+K(XÁ^xaLH;j. 7~d <\14LԳ'd(:L>H=fFTddɔ1Q3`jtPf0PrFFr+_" +CZpYe epV]Fښl2JO!G! )E\l+ks s tKkp+\ :2Zlyw܏953gƺ5\!PHQ4GٟEEEFFN^pecT&5^=$^MDƻ=Os?X@9ju|hhkD\Ϛc,ߠ`n{j.@$DKjJQĨP{K$495eppl!t%µFE\gȸ3(t 8͈C! @͡65HOS,H3Tx(y;W<ʘ(! OP&|J~zjo 1:~ AX']t6΍c&7mxc>TرbIt+\x 'mŶN 9ތ{󽝸wߋ)8V.;| |w]`_Oe⭿*.\z~Veغ=2ȧ?}lLlKoBeU5毼=r9'8djHTbt4aqEKهȨnżx <}S%ӏ3xRJwH3}2CwIA 0$acd.PSϺIV3svvvosE9s!,ǻьRij(Iӑ7\_3j|C݅،O`ko#~EE“hǁW^̝ĕM{Y?Yd뻹$W'M1-?d X0_gK8Y"Υg#,8 aس}/2/! Op+ao?8W֎IBNkx 70\R^ " D "ӌK*1\Z׏=&>- ` 6`^t;NuЮp]1i{1'FU^J*1-^HM|>IJou;ꐝQel\$,':9(4k*(w`6FQ\zB6^ϚP̎ &ISÎ*, m^Swydi@XWJ`7? \DgHj,X6ܨ-PP7vF-|R۹4QQ%iŕ8qFB?4Q*8|~;kjP_%l١vEё]xD68D$\vZ2j+ ~X%zb]h*w"=K!qb0xV,h5GGXW/#Qf/i(0XV\\N&&6A_\/]kйj,#I{$k<|uv#++F!9.O;ndpcDG}L.苣5$$4g[L\f6nlxo9ݻv|c!s>p w,J;4Pܹ.u,->~2b!84b0` Vp1JÍK7V̥R:Ĩ4qjǑ >ay=xy )'$ %tVXo_^w4u8@(ǎ7 ܈:|g]o|1\3<0`^ It-f#i+)s梲vRa=3sraŲ IE A*e,^Ȃ_P$:pk]<~ݭF; ܵ{/ K+t*t4sЋ3Raޜ0lC9\WS=oM dXp{3gd&kס~8u$,ҁ/woŪk1w֌^gϓk*Kpq'( n[|Wol]G߆jhҏ⥗9s\]²bFB/oAT"7 ɸmR;|Ez×6Rv~6wX﬋8ߎP#94q;3c-<3Z~)ލgXyQϐ{ /;ujq+fj){92fL6Rp1gg &>knKp\|h󧙉DػvĜs QDz7 N*^n=H`cBה`_ykl4)!Яgb7{yy4q eehV[w2`Fg J ٓ!)20BĆ: Kal! xeB+ +N(c$gddڪ2f቙-[c6xz6PM 2/˟6t5,&DSn@[EljEcfS3"^mb39j({1x$:uOCtLt6+0'@݁%#]|#T8*=$랛#i\г J" ?R8}Is{"L&)t\\=ӦFJJ$h@rKPW?RܴDtswءi+_sq 5) pަӔfzz4g!0c#\G'%a&C@q>';~#,\A[p:~쓸eN(PmAXP>y-=L5: Ρ y)(/ÕZZ7?FE~e%dV`ӻ>-pDnN64aTȻTHͰfi 21yД[// r99>9/sVRˬeۈa5h$F#^".UkSR f+:z;IS=sKdwUnߖ&BfX40F IY^p] zzڊ苘9;v/)BՍq8TS6Os*#]Fi:HԵVzYF]|+] "XM?1Kf݈0{vt,X!Uʼ-bYxoaQE7jFIMvyNRXаPOBADm̃+d'Ǣšq&7^ٜ&n_+=+ mO 'Ođgoє憝6Q_:-tx{זM9͗̃+$.6XLjA2%+d92QA݆3M[1LTt\*4FNʥ9xLoNL.l2m<DEJ:O:6iԬiJio2d4PB5,%?̝CNQ[<£bJHuyC[bYz\6U)/烿 bb(x% %]fiC@9oN un<1$/HcۋZ?B?j9v,-9 acJ>r%@޾|!<~Z25NC(3|5b&{aHA{%28p.j)sUD+Gr˴E̙3ɕizɥ9F0|wՈ5ť!E]ױO~Sŗ|G23WVppb9:tKr힇܋yښk34)n.P2D0̴ ӌ7Џ"fWzA>^ƦM {uv$CLp޿Cu{??yS޽zA- *ޝ5(`ctV䠯ZCTE mɘyY*Mh[% HEJbb"4¸xprsg7}#^ǹeiý8kۺ+W! Phv^*X?t?xN~O>13vl8w޸~_ V⪳d\JݪQ4ǡ` į[{  "@ {F`cL| VTϩR4$|D\&g7NG\1'OKCjEdL|/ lcTG\4V_N >mZ|/^_]]]֭[ x뭷 tٲejrIIIVPa^Q noF~e#ÓWSS|6fMW)eV2Q$hյH ե&$$@v3f|69+SNSfy;K#mRj1}B{/ S|{ĺO&55JM^8ߡ-We4RBIࡏP kOnCdmQ}륵D"ʧpwƯ#4YțSR\xȖ|I˗iҥFp3ص q0@4j"\E+$u=RB@RXXx9i͙KD'@y9P~ .y5CjYfAF?q+x;n80F ,sgGi # $$'c+jy&ugϞw]l۶m@Ӟx |eռq+|IH#q!u&//r}>CZ@$&??H~aK3P[k+pz=9s[p\;DHG)к2eff^$/sۑ&~jC6VXqչRO˔#^Z k_[ڛ4Ǐݻկ}+A,yk$Q%0v$4n6z^7Ҿ>$ =_(Idf8:@ i-B79^ lBc~CsiW.F,fmwW8wO(/YdX8Rthyqlh8* W$(ӕL_C2*! +H"ǥǬvIH`əeP#/M]6 v,e^އ"'y2JsV4WT婧:q+ĦLmV%t#Y9<`h* TĺW:^8}{V2D3%4H`J~5 /KÒ2ϻ^#`=ęMG zg_q4+_a1y/Spl[fƃz?j4h9PIK%X^;˩svo㰯9ݙ"Fנ >jx IsIZZAIQg&iߎ@fԀ[]֭3>E7A~N@dw5i\D(*EͭY[}xy6+o^3c*#90>JMD8~Dz s8` 9H>$& ,XhUGԴ7;۷o7ڐ" M;wZ0kq]w~;4݈:0;S& ]7R0 Ĭ>ni>?xg.tԫgD^ U0M:Y(xd&g&Jy45D[[?n#oД-[@0f 7SHQJʪs=痫(4,WO"I7쪉.<.015 &2b<|Gh֕ D^jwRf!>2+*.D=3"N|ZN_Ata[{=.m5HYfB$~ 61:r񮯄犒,q2H9"#SRoe|Fs\T~@6q@/9~.nz27rx8¼3@t2uei G*cULwQ~Q%r\3a-gI-xm42J0ܖ^so؅v͆5>{PK%h>gnBX|*gވpvAa#8_R{B=\`;ĥA+H߆mZG+K9:=\;Y軧 zdZYp(AqzJϡkH+1$`5w$x|7B+=OU z-mejKx+rM@H uԳWPb,|ή@7x뭷YRIOd=TL ,}.8Gs1-i./4dcy'[fb绛># vNnzK b(8k.G^A bRq;ӱ׃zt"ܻ;z˖S 檡.X5- 1xIGd}`UDZ PD`mۉSޟ$/S;N^8q/ύM)E !*]H+. ޫ+ ={vٝdztޘ<>?Orr>+&S PHBN9pg"Cc*o y !2mIq(ήO'CqNc(<%P//_0"9y"SzLn7IbNYWN<)ͯ2hD %TJJqdixz<-Bbہ+X\M/H¤aL!m`?{[}ts0ncx;L308Pt?lpw|>ca?O_ʪaO:C(ٸ@1 ^ik+pZݪ|.`8  R{uuHa4%ALU'#px`Mun8  OuBȕirJ8kwMkMp+fVWV G%W :Pb<,JV5`">$8KSB'n)U%j )Ih/'p&I&BJL^D]YFO8ɲeԫ/KmVk`fR]/LHc 'dʾ< 'II1R| IH5KvA6pv5]%"$U>n< eŞ-49-1cF#&؃/W.eJ$iSvaR\V^"^nRx X.51 >4ȼ&JZU2a=R].UeĨ$ c IDAT5n7yed) VHclA9q.h0fʻ&? Tw$AYT~ ꎦT0&% l✊]hʲɀ5 $'+C<%5SFc)˘`IJRuk$(18K'B ekiv/~Z(ϒ#;&9&~4=q|O[VQQj֟Tkjp&{U$PGF͜#'V8.䎊I($:J&%y>ْ:,' ɈKhNf@ ~7Ղ;VH*/ kCCt"{_?pԶ q7׈VY9{@fOsdr1^O!1D Xáyj , il : IGUEv[AadcIyXs?eGMA$F(؎ɁG/˲g>9r :ĚϜ K+X=5!ȑ$i@\gtQ_!~ؚ [n|XG .}69z2]}]W85}"Ź{IiY{G@V)yO g_-i ްޚu |P>xAdCf2lYs9q7I\$.A18 kϐ05{-Lq3nrb>DM2dՋYDH5 u-@KIπf p3):XU)-/FG !h 4R+dҾKC4@&N d|9eȐ!HnۘY2(S`:M4L%j4.Bfױؕ(a1w_=?l 5"Jw7H%AijUţF=&! Q|Bkm \]Mw ~1+&[RR宅8Kpl{XnȥY,fJvsQ#ʚ ۤ9;}B"? e/0r>p>F#.ZHA6 Jjk PH6ɔKCmڽb><8M6],"uxybez5fn k]/v/+hl`op$wO6jкR,7Ȃt5 t0}JM;4[@xβ2JCOPp(V#H;?nRvX|e\p)̃+VfD"D0q(a9]4"2q&GlXT@VTSKcȱCfeljLD%1+uHye‡d㖭 (Tؽw|ɇq2EÇ.g=$=Lqݱ Ura='#pXQ*kqW_)#yŒ' 2EE9/2tl++R}\[aL!-Gaذ r .ODb5qe-V//'G6o,3sZU˱z)i A._̐lRkښZɹaf]]5W}r9έk`JDKiieui{z>x}e 't 5H™9EO\ƅFi`QP`:F(+8,R=3SQ( V\Q(PuS$< ;W]R0c ٺ};2k 6YXBg妛A6Jd=2}`$7j 5Z*krϝ Ge̙X0'-np+/@ycK϶uU۩=sf_\O:|MX@8qZx z^&BO&@$Q1-\P ]W|ϓM+ !)CY+Y\cQc'\W)~eUPfCGs<10FI;̾ P7=hNX"=wNz)L4i\9Dpt|dV9,4i(CGp"@6|Z &s<3GcpXZU\1wܖ 1wi4wXQ]Pf̹M="cj{D0@Rzw e<ōބ; Tv}IqEJl`4=)|띷k+Yy`ϵ'uҥRMeme=8doDhvϫ,PtEC`>\8;<~2rHg]ݿ0kiϜ(_=KĎb)IdkU<'| X=BESޭ:.l ;:2ZhpA|hE?Qs?I.\~WNC_A ~bHSHZ4/$]a~`!`,_v?eyyývLuwt}NB1ݗ_~)Zo}K%I{cGg_H6Hd@<$ 'LR4\-<]Ow8[!crj)`>Bg] ;(wQ(⿃j ( ~eŏ<(C2l f\^;v<((F+GozuWqCw?>U|~b?\X<P[^$Y/KHBDHQcm8@cqu-^ő mnlpz oV,C}uHjqP]" K(j3z':،Wr))P"|\iGQ a&݉k-CGkG3HPydk֬\t4#AS ,Je}!X^4) y@eEY2%^ޝKd'd}wʤ`s6{x[PqxЛv,-{PSKYۀϳN=Cd}] O^P ϓ3'K|8c IF~RIjK.Ȳ7WʐwHSix5IQ3A 랦ZQcDf͗ft_M_.w?|$~}+ z\xNxե+qTrqM7/j{ \w|q=QRo!W|f .+ Gɡ}q[ؕ)ϒ49cVzȨ)eX 9{\<IbFOɗ)!U%{XT-WNoR ; /0Ff2Mw .eAT S#L K9kٸ$ hGyy9ZUe׎qZҔqbnN0 l@i}A+rrY qG =4ˀd4\h;=@=$>u'R$F_=\S_ȯ2<^}U?~4(jk]mqMmQNOȃ =V}!.Ez`;as,rb½+گ^'AnXVrp|UftK .iZ<χ,YwఔoC:O6਄R`2^+R''^-eH`tdfK@]%`9~TJru)V@U\-qNOV~^/?\&NWh'BB=d)5֮/- #?qz'/g$w':x$M~':)d[0?OliZ-ҕ 9ɓ'ѱ߮{ph)ij̃缃C%+Z]RϲZOox̃xat>f6lתF^<٧Hz~̚<%R[$E 3^2% $1!\/m[K>Zޞ8m 9%U2n<0*2h}b5y_z mE=\srt.t 2rW1,/4YqLɈѩ=UF%FH.ڬ "3VrQ\/98I.)O pW{_3=taG Vfs@gލsdl[:JC[wd:~p_7r7MQ&ӦM;gjtG DJ j}c8/P #Q.w&OX`@[%jpIBbCM4<:4!Aa8&DAQxdXPiiMнz(_MRUR n8`hO@zt #k=@ 8rvNVxhjoD_aaah:hΫ?g\*v&o}>/BAAurM5ӥuT7xցwgL@"c;tW[P׿U{}N@?;ܸAdBxm>-_]+i}FńJ֑ M*'2dW$墜(qȀ<߽Jpf(kO,-HN}e8ٳs,LR^%y2lDʥR5<^r.^w8>Y|q4+/ 5պf&Mq8ovoQ$_N^Iҕ:Skw`ݎَl=Yh# `x6wUBqf+#oo?G;f/c$$i }h|刜P#Q #_SFO?[6_P.K_]RqK8%⋭9sō~ΪD|(b5hk .)芀`Pv屏'?iMj7[ !7yz`?~ A28)QFςM+a# S+!r藐4H\@"C#"HTM:pŪ!O`|{_fo%J/JI9o\=BZ O<*~$nmh;805 @̭}ҥjfDE pBD(&7Լ!Eztp`\0P"BmLZjFLkZn+%+8J, P! ju`@z? !Z0oܤ9@nmɘ1cFSAV*2&X+ NB1W45,ϑsk3(XS@3#-(m@YHhdvl+Ōtih()h9!!Afgt'A#u<DրMi29hGQ?@aat2~7SS9Gp  KO=PKz*49%u5UʓXG^ .kM0^'C70z[AҎ˚ue4,-,`q2j ;fs&ٹUT@vծZRęt>MfjٳTVwAg[o)# yeZls?8i-{=͍i1((`,h<^Lc8y^{udg:@%t#f|d"",j7Ed栴8O=$闯Ȁa,/BZ#gZ ;n/=O*!@l]+Sȴy+Fc3HI9\r.Jv]7oDcLV,ٿZF*ΜXj(>EK sٲ+r`܉1Sd!֑%ٲI5f4L$=":YaԺJ5`yUٷk*YR%1ᾲIVq܏ <͑W.{^?T[tc؇Wk`<}PzE-T\"a:}$G-w]̥ٻ}2h)tF>$-{e+e<ާ)Yi N$%p\:SH;vdS̱r~,=2+/Y»1##CJ1Q5jToX6a vD%5$_l<( 3%nM/qh;5cnjmYqY #cCpkߴ\!uŒL&))zOͺ O%چ5ag,([Jd:63إYSdaCQ%VP7.U_wT_8{J#c_KTCWq)o .!Ir6 c{e;%2,_}.3mݶ]RbnV,Xyʰ! 3_L bc_; YW%/Ai}lڼCJ+%L'a RyL`_SZ ,D~1I3<g՗ߔFʶ _'6x!Qɧn}D$3N#(Ad*N\= BO˔d+dH <􉕫6`"fN]U-{S"`jX) -؏"{y(][uZ*)ϿND>p.]d~5 _{r+gO [CG  𐊒 [x\ݽ026?^xHrUtN1Ýw))))m /?Xm% UFey6_tQ1 ɽ(ť L4Z`x]̋%MΜ:. eIb)yRQVz\4_ZKnNgDlL圔"xbJ?+&ɬd8=gOop2J +>&\=}AM+SG*R0zh.KW:2(#eЈ1+SjY)0R0b$yrzټkTLRuB<"%yeec!h L;S]$n%S׃Α\lHsO^wJxP~QvFi2,B~#BC,lΜw][u::n\@m YG1=4f(GCܹGJ 3/].Zij`|;55U躝"Z80"2BڈZ ƺ -$ 0 ] J2` 4"}UM-Dk fY pE\|WnW\%^A!xG<[rÖ \= H@H  kqIEkR ǟ[̗QC.X1XܰS *%2G~TzZı\W&PW+(@D8eM]%Uu_0?_>w{DӦ&Y }ʏߓ2H>|)ze4Il0#_R#q񓸘Y1 4Y)"!/[#=Igb_ӧOk,'G0gJ&V!X>~r`YtVJi2f&S\"n>KOmj\4&Ck8do?Er0  Rm6Px6M.[6n j}jvvzhxY5br` lЃl߼]\|`r.c&LzJ:X)h]p\+/w9]& Uu2]c ,j8=sL<ܻg')^.t 5F ,74F*K*Ҳ߰ցף6MjǻlZ^V: oS *^gM=jws%&Mp:@bQQY!/^~2tP\c'hf{m> 9~b.Ubcu|=*\*%$0Xs/׉w-(AlDfCɽ-qH jb5V.9 &V\Hr%1=\r۩~dIЗʖ% b+*fǤj¦ǿ1[Z&+olH4:-Zz[Xv{lw.Ȅ4x |Ćp]Lt 6*eӶ!W#d_bYxoHdĀvg$߄s쳇O>F7jDZ8V8uo(gL-'ɒk/!r*+I_X$CF1/ \Dkxb-rģAkڮ;Q-#ARG1|# jgI"bhsh+,}쐫Ő7A󲏃:g6!uL?eI06fEx#ܷ#OW̐~K5N s.O fTDʷtY{#~TME7߅+A뎕Khz\M\R-{Gr H|dm?~{>X7qF $࠺Rˬׁ0Wwoyoiu|orwoZ]!i3BZeӕØ9 d3IǪF^xˣ.I%d6\PbuI".mBm=s۶ߟ*M*򆞅 N+onl/x?V/iJ Aˤ àq{b؇ұ S@smb~ Ķ &-IFc8+t>4mXmDzY$ ONQYpfAлPM7 D: -Jύkhy"_ 2dH]~r9#e^,@&WUn3絁<0;u D̗0W&\؅\QkUx4ٓ uEKL2,- p&p:ӂ.(05G>j*M$ L@mȲLXC_q'IV` Rv#Pj[nE.\( ,תud}:[Ç{=]h"uRJj#[w_Kۿ[g_C1qj+f*H(@Xt`oˁ>nG 󠎃|@X/W i롌%lu@ׯ7N~m`lb~"\=\3%WX1PRR"999Z֬iKBB=6]H39s&Mp1 䀯*GΕDgDo:S'[T)q$Rr&M3ZvX]nNؽ{̟?_룀mCye5\lM,۠),YrMwb @(~⩂<3CYx:Wʦ4PO95+3f7M2o̝;:gr?%K/i(z(sIkhqFn7t_PiYŋ8E=Jo+Wo=Y,2lYv{yNj5/DQ :q:?h&Hg<'Fb(y-7(q6e͏γ 7ʻС\Yq{kA◥KMaا S@Q}{9Lw'>h:rxe+Vh;HSj̃"7_O'X̸˗@ey48"Xܹs kڡ5.ha0.=1);cќr&}\qp`:t %Lۦa: ـ7ʻm]A:[ hJ,+Zz"y2kҙt#*fOߑ0ۇ~AGcho`ҕ zr9`s ~||F/ \} =j{M<٩_G1R+jW Rdd g` LGҿC-..4Ok~^=hQwA ;ֆt8Uuy,<d*zZzG͏믿N !ĸtP) t_7iLAUo۵G|Hz!QT|#oog08Ҽz p/,ȡ*Nj̙eMqڛ7oM6i3tU&@Qf:ݻW}]mfoJ?H# ]<̏7*_O{G$c_gU}0ST xmf-e۩v ge(LWK㙆+\~3ߍ͖uʎ;@QիUuzf@\DҔz2'NhLƴNSqVfgG~STG>:Liۑ<8-TH_VK*jp1V9uc Rmu`Ad}F-_[m (ꫯ4= W+g}V*Q}fE*vt[ۆ=ؓFSYd/A_ev1۷o hԡXY-jsuokzњJg\mJ&I`af,JlIUsD#.;mcL: lCP̃2z@3;vs=*zǴU~tT%aKq ^k'3lGkq<6 MdħFJ` "OK'mgp5"f{7TȽT?ÚĉWٺbELnz^k}'[o #AFVbuY &RɂUt\J;وmDb5 E4yN~Sj '6*ҼWd _ךN7ᬊ{Pp͍D:PaTnp^XmO<80\ lz~h 'qotMdbx 5oH,V` I7op/,g`8$cwz捨DS]Z@T檁̄HanTpU裏4diÙӦMKEUf2V5tsð7h0ɝ{ tCGٳgW_-FٝXA^El_~j*` h%'/k 㠸h MtB%@qwiEݵ w{B_4ȼ'CzN:W 2f& Vx[1"ZG~0?P]lS5S,D]&}&ԩS+h$3gjo+ޫ!LJ|M18XQٳgLRQ@L9pae@&' W\ e F׮LnZ4_rʝd?ĀC)-fg07(_l6hA; dXDE[[lO>Dmۦ<ׅ~0Ր?3 އTywj`Q̃"LB 7> y4VN5!W7{bN0\Ud[*& ۺ]0H=7#.g72*¿ojMo_}G>$2 *Bo-SFNh*LE"32!/K f=<\(̃0>'4o-CtدiBG[u`Mof6a0K(6,Mt]ls-@,ߠw^?;S[m8+{zE)PdEG❳(2"OYԿ7RC$pUCfBxt P8p*3V\9Q?A 9K1`w0Ν эN3!(P7Mo'io)wL;7V$(,Pe?ͅ/_erwwܓYg]ۈwfQBq`+^@!1 5v@yLەִ34Jb`V}V*LǷV6!9 ,MD&.8aM928Oau 1(¡{ L]sA;??چßg6?nH:1UƛRbWG7MXe2"1N +A` %3%n9b` VI0GtM'0l;ZvӲe`Ři( HAf^|Eq$QC_jW|t +va|{z IDATC]vĽjدWvDq ~=uYrȢ0žb340aĊ+u6YHSbbb̌`2\ZaC|q;f|߈V#N2±I\u!P6G"M2I$PW~*. =[ rr٣`Is58_I (Tdi0xeAbmݸapVÎ LF9ñ%QRwtݍc?#7GwLĵ.D⠋@`G ȾL5a?G\s%` kk0L$IW^-iGEeGL"pF O싛r&]J:B FH,"FtP#2 *&+dK}_u332vFd(GypÎikBqcK/;U8hJv@|1#CF$iT FZ ŕ9`iLs# s&IE(F%i0a"1 ݕLDLx`: 8'BOlOLxz}Y# W6ҭ *? X*DQd I=ytyaqVÎȎf1\"cj&1@Uţ(B d35Dܿ- jf[vNB"/7Wu7o&-I.N *䄈a6/fYeyp p8ndj5V$,*&Bka0 3Fb?D\A+DΚ+֊QSE2J4fAln>2`EK2N( +d`iV ^8&/5Z3pc'TӖ߶,yqPXWMuO2Jذx5gNޜnj-i`%q4\͠}ć}W2VFVEœgz- QV&`&` axdd$ݦxТ "0 mpGYQUBՀ=P.Л5Z ʡ U'} #QLC[az<8u8 iв4f=^Z[!xӁ?Ee^o8SlP9@P8HZKAHWaq8MխFD#!QA1ueBǫzxCײR.ͣ!0^tC!Ҳ^4W.+8GQuD@+H^tOة :0zx W S0.Ey!0*`A&|2VsPW8%y": [ N@~ :TT'@|q| b*~pSQw vgPnZ~d&ĝ?^ 껜LU 'DD([1ML @ h_0?0 ?XlҸ#NNT!^=ɐ(+_LV  Bh{/pWzޥFSRgʑȕk-(aHnR{A12 [1eic?dXvya0Ps6p@_+]-(y^ ZnuFA#2ٞGoN43F>W(fAꪞGK>,8uUebv!zIN1xUH=2U > so +j畠Ͱʃ $2 }Z-?*nk0;}?_R ;28GHg.o~^}hxLPfpBA^ƃ7_') a1ƙڦ iTT+thC bæAFvP 5C&Ns0tBx .Œd@P6R 4s9-L[ѮUFo6b(|=V1{gV6]1S>Nū8/6qQ ލ$˕3t2wE}Q/zhk@@KAPm[֢͏@U'0TXHi[[ꡞ|5H[3 Y H\=ŏPtz}|ub@t^AN#%\|܀>`XYoHoc8Y/toT2Muۮ/EjS+߭+6wQ>I&BB{BfBt6hlIpp-ud^hxZW {+Ch:ݽn ?rǨnQpQ +ɭS Lv;i[VRm KiEt49@Q397B3T3*-@pe2%ljVGfB܎~TzXE <[В `ӡ#@Z(U K)U-|ԇ:|  *qWل͑t Hf%օhog(Swʳ8:󜳥5HZԛ'F$zB~zW tGTseB2 qCc&nAЈ}0H;m GuP?XJш>zqGlS,  q pE%p a:b  BI-TlL)LJ":rhԇ{ ؝sP@22K :@?Q_y\{4 Lg#z9Po&~) #Ejx_D(F0C)>Htb(C۩N4>'u'ٽ? qz< Ĥ F߀*o1I[ QʂQA @L-L-W WY]Wd}sp`0]hz{.Aԋr'_.^@'$3LZZvRw+WK/ۛ DgjuE̹8Qa(=O+ahP@?NBd$d(NؿLq5 ):+]~C崋qE߁:šl2p>~w6P}  +lKu d]m6 4AÒy|LGD:BCD}P7SQ])(Ġ@7SthA*=,T6 7ɮ7B+/Qԗ^}|Rע/ :ڏQ`!p9Q 8 sd(\D-L;4XJ]xn 7ԯI,ARYp/ŷ1(s2 Wb|b}\@q@:0@REfnun15sij6O24} rc=v$~!(Vc `P ໍ­@~7怢@Z.fKd8u$Оtw}a9am!>|::#A(=[RbP@)Y@22~@Kp7UV+n3P4ĩkaݜyb hX{We!]~A֋PN;cdkPRh< =QEט [=tzpb< AҔn ̔î(7 q5A;Q\ &#Y48d3 ][m2|=<\{=% --Y\4&{0:',.2J=24(`P2.}uW?Zl.8A&EsLu.\}'ڔz}//ؚ(y.AKlAS>"R:`d+!?J5 RdEF^@ȔI\gmn^~Z3. 8!FHPFSoIf)L<@*癮A_` @~epMwV D6d$ĉ@9@俀Wq:  s>P Q0(`P@ 0n#x7d*S \Ji2t6~ 2) 0(`P )1$<s=ʄM̄:֏g.&譿{3.- G}u[;^A0<1)U@e`+p@LzbW244[$L~~`PAv)WH@FOf=E M(SH3^Z`} M+M߽yq: fߠAS !cD sFi }dsS(G W&?xu V9TsN 0(p0fpBf'#d$oĿ|eGDFB.O8U9kPA0br&#d&C,d=jo?@8mtb h0:QD7B~ 0(`7B0Ne(1P9k&cWA}z1 >Xt?Uw;F9z;0D'Y ބ2L"7{ȴ5  3Ġ-6 )헃 :4lر!!!555.DxyyT楦9[r劔ˀZ:;S,?*g_4DFF6]|Y tY^<д{nvE:7{‘#G?!C5`DDD.\pIJJj:u+rׯvMIIi8}ZXXkVTT4ZMآV1+$x)p _ x93ӗ)(e,{S,Y|xx8;ŁBWO9ٻwo[_=._$ V,~/Y*++WԽwԹwi|N ݽWOOz Ʈkp@| '5`4n߿@WWאFWeZ_Wo mSĮD4GYLLKCC , e{]jJR^ZZꏾ ^[GhWO)˰$''ye"?gt+E_#O 8 z&f#븾pu`t%)_d/&Kr;L|)20Hxöm8@1mJKKk eu٘7a@>\-NCU۹sgSuuuDS>ogkqɚwsD1@ej\SWQ5r׽ć YoG=M,̳憉{iłzI2ҩB? J;`y`.2{۱" |m ċ1uaG0qm"zs3(붾s q7j%[P~C^7 ͍0C74ԷA15p(u9zj=A{ywZPֵP ۧi{.~{ÀTW#GE0 x@f RݵUsAt X7HZS j%?/.WWI^~]nmn5M|OP_'ٹyzr{X;ƅS@y\dğ306 ݳKG3P<"[(sZ=R]U!9R|DjՋKUUa*@ 2Ɔ:Y'r|@7_kyi,=E]ʪj_ӟTILiYP?Pi:dB킋g南ߒ<5x'ͼꐷzҽMcC=|wҽucNE9C k-&LWSp7JK &R~$x v? nm~j; *h)Yŧ@GieӦ'!>PTҧdpr?qkQZ\|Ia,{]JLI w}G“JKC|pX6gM;(|\%T}ggS%DnsKw|0«wqWA9A$sIIE*PdIdId^~먜(Qs9$@sncl0QWS5n=8w*&Oľ}{P^ۂk֢,&RnSp5`{1m=2o^CAUI1WOŭ|$Y zTTUڵ 8w8w3b+`+1#1,Ou-Y #GOEm]-cbB%)?65`h2BeY=sq1xSfAj ;c މ)3!ߗۛ97J rp-ܳt97*Rq Ec(W)s@B\:g8{% IXp>e?欁y+x{ QI"8?03Ya, euLU ]UaVضK-^:OA~z.̚ !>.dv`zQ.\lkCST WwWcnp>믡ۿG^Cر >}u ]S;(6752q8E28pyآUޞCuiz}w7:r%xسWQZUG戣G,TfϿ?W;Ђ_W(*ژz |%\g4`7b̥}O3.|44/"R IDAT.e#y'ŻGlBnF:6nZE¶&/M~v76n|M"U l3֋rr+S`\/MC09 y<1O 7ﭑ~bLK@l|4RS80!:#po=,GxO`e&uĺw<7sJB{ lqQ>zX'ih7c\2#L3HI׀ ^AG|=jNy)yB`aL;._uBظDN f- >{;Uro][` G;EijlE G3z2cag[6#8z2zCa^ع}+ qBE-Gt2&,+܄9q 8z@Eu?sE7ϠF @qE rR<iI=s."-9欞|eM8ѫhDvʍJ/1S|!1X|[7_$MO) CW38aRL((4sM# s*j>#ndxHae<9t{ (aO4?/lhi\]4> o izQ@ᶍb&%;/|-5Hy0 gO߼?"E_Q&bږ]K99 {4h#I 8KVq%9zhf7Fg0-s4 7b]8~g{Fbӻ:Htm@[QxgcgGh,=a pͱLMJഏO4"Y,ϝiϙvsTZV\Tq!0 yǿ Ggw,Zqi믐YTsﳘ5k@/(*L%>;5= jM}d pUNӖfϞL3ILm̵"#\Cqss(6p2NŕW#<<'@Vn[ tEAq)""#i$.oO1Dڣx흠;뺚j;rֈ|(-x/@a;G26; jÑVIa> L^FJ|fE\#gLq"8,n|'qWO>֭[uCTtS@S4(c乘H`׶_uX41 [i }W]Q C77NUO%GjkK\nq׎_$N!E9>ʣ火,)xxz)ՄWB︨ ڕ\AW=P_ҷ_эjr22oO0]:.ttGp-biZH0[lX]3CSasu1_nm0[^Mg.u۪h,,,48p;4\>]JJ 𕏝@U"<^(^(^)C>DfDB(B&" *0 ( !.VՉ򜺶QLS)yZNwiLgoU[;P=z<'** *Rw#uRFiiRKv^Ng;;eN37oH9 _䃎j;I:;,I43<wdIEPЛaC/5de 󺒶:֙mhd^{~T8>Yk^>b95-}YMh.u䝏~֬Y:~trj GD9U>ApL_G E7%ҋ |3;鉋rR)BPC HܜYYY N5ZsY{NuP#ρ+JMRFOJ(.PF JۄJDhIlJ(y#9j!AzY%S~dKS>L HXl" Inu"X B*B-^ JZ%J{ũ5Xj-|K^ [5jPFK(Ɯmў !AUȨr38^pu +LMt36Zm&)\Yb|0'Rax9:Z\s"Ru܇&Z%V/GwJp)G'g4S:paכ&Ih~kc'/8 i!e@h*K^yέ2:qvj("eLB)bY |+um4I@c4mR_!Ҟ;Ͷk|-*,@K1@%<ʙ&}{>i]O>ދ%Zm/탕' &.(9QAL³-]9n$.J q4hMَV *97~> b⬀&rJNŏvB({ )Q@H|ze56ԠdzZhj<2lb@C}^4@Sh]֛Mx ;wFp?GG͘>g.;EHGa1Ӑ1 00:Wcm7֭Gby@6 ZڻmvpW]#kqiJKj&Dpi =ux/Ct$|g1s:ګ?MJ*Vn=tp/>Ici^}+6bR<y';bŹxwPGE8c>]pd]njE+CbԼ3p[>ye4+g~b&۞+b'o< ? Q=aʝfٵDmq:nVӦ ]]7=֬]C3yDAE-,[Ox,ną+wrǪ{C ƜUr5R3zzB~;Fv}U7^ÇÍ8o~_P0Ļo W>!EVS^!慂R#nD:H˻ػmn޸J̘HtSd݇9Zẖx/i0OAHk-(*iS<2ȨGюiE@;qQ,/mTbnزe' %΢E98KWބo|Tw"//]KC dfew\Ãq+F]imOA{eo~gb8ؘHl+n*S'RJ:xw=oN][8ziHέ8p"\9#H֯* ػOQYߊc{"ھ9wIYǷ4yNWj[k[GFi#^zx򱵴Z?\l.2 +^‹=gNg ԰'e\8="B:s+PB˫D=d^pGN)Yf (GXJ eSݵ ##2B:Ȉ鴰R[e-4tb8yJf.XD%L¯;|0>;wǖ{1Qajjp,7 )%5Ɠq \s )@MWTF/ٮg߅BQ 3pyT*{Q%ovDhB3";N9 S0;yJujM ]}1l{X2aHr]0yͶ 1f9/;l1}:髖N#׆:;8ywϫΜ.\P8wS4{L=Smh6- M|Gâb!^u~hW/xO=$k(8Ad ?1B-Ϗm*vċ]'4MNvtri17J8M*ڋ5k GI֭zY K-4kO|tޜ[oq8=(Ug [ U7ûn!eB4p ٕ$~1>\PvKbiBX b\d l0/SX~9)mrmF׉^u]4un=r.yNj75TK(VpUM^ȶ*T`("lvD~߶r*!ɷ϶䳁|ֳY|k*Izĩ2^x%ujhm5ܺua3@ P>>;hheqZU@HP5qk 0t~IKA3qDEhi?ڡC,&%/ĈNl6Qp0d~URV { C( bL'_!%PQG&\+ UJ(H}{DJJQHuw)|<N9)DKba%+CUFXkHcz@ڣuT,n[$~7l߾}G:mQC*6AٰmT6Jφ t qHêz᫢d&ɣoqm-b9,sZuK&h&ۘ5eh+ Fn&n'J"`CE>i^aK[x7t%B+BEn./N vqqR88iW-5\vv6BCCu5|AjZ U&9{ot5_PF/_ńuTr5Rv-w˞*z⹛ĭ~VXkՐ5Px7߈8<ʆ)ҵٟj?K+4 huQҫOvdw̻[qd#LooX1}!9=IA^B¥f"V^"^@'}|GL8GozVc,k-YXY}怘tWϲ_sY׎>RVfg\ aG̉QTVraYL+):e%%z맵ЮUmNnsfۤc-ܛeLS(;uYiNU/ <j2\(*.vS|c0Q&%jN•kz RSR"#Jj ƚjDdAwn},lw- IDATeGokkUwDlJɢ(,p,{bj.jq==t)imeL#34(mֱ<@~Bt +[PX-e+ UI!(|tq%~w(+VuO+k{dg9'JD-Wǚѣ5trN4' F̙;.}thjZb2n\mwkp<1Gxodx{uʠ09`Ji$z5n/?ꥋ٩/g؝eMN]{MsњHN~w([o[EIR6}%,] x~X0#B54u^C'ơEt?~eUᮩkS.HvÖ'i9HEA<i/i䜱dtDubSػ%Sa`HF;>k)Wm&`YI1զPsE[I+Gk ";DvX%#2葟CE"oI'D@)tJ.cUyi`"dݨo|[*dqdNdw÷a⁼|,_&/?7_ê'^$7AXMkgtV=xwiZ>voߡO~cx?wcǎ:g9-{~Zi>$n 8F <{˸`OV@Һ#~4_˾:^3O^g@Xܷ| *𽗿:b0Ps0㣢]D;sfLyLCJ RCރgy&c*5(Eqsv3t6l.=139TV3;a|Cb|[~n_ezp~=v r&qT|b; (KbN0N@RR8-}<wBN8 +ǟ~QD5k9GbϿFаȩ3ࢫ'`W l=Q\kN14"NS_MTI#C1sz.;Kts,yt`s=q^ry&qԹ(23h9NdAPyp:9Sz;HVu#=-mL#Ғi{/p`0=V<f$\8xY9Sh %2p=m0l!ҟi9"d>ɪ{XMt4L9~p7N=?,BZs.uIY\Oaر{ʫ8VQؐB&X|ua>Y'prGZ#oc'aX\=w Gnz,]0[YoѐёmPXr_8"8RʺV qD,wۀDrw"&LFy;Pիq[`hX/$Vq}tQxFv.bóga_X2Sm"z\V@Iz (3׀z=<2ގؾm3v=H!ĵ/"6"{b.67/?ض6P*"^ EpX+> W*6s TQ\w +ArSuH˯}, eyr>ǿ%+oRˈ q#Zl߹U5ubsp1)1h mh⣹ "~At_|rDDbdȔLϹSUKJnq g;Qި#|/n<+̚7W9=,Yޅ9Ӹ> P+.LӉpsqz.<q S*&C 2>BXa6h({TT={aj24&KpxABw%!@݈ssA<[)g{ .,*|\qJI8zL!dy\͛7MvuiOBc: 9/r0 #,4pY[ ԁt;xJ|ĕwĄ8Nr,VV&1-\ !x LK1k֠vvӻb=-wǓ0;2 S&AǎF^I_{?4欟TG*KCv[ۨ*@ ] =&qD\C;r˺@ژҺĄyAT.ݹ0.Ņ®V% MP}-P1TBG_:qS$$,(G$:w*]*΍H+}R!XY[+o"=._#;Ruey=DlA*.;TԶ*9u+W?u.Uo݃2 4/~n{{O}tcT? eP5FG)0K؍a@@D  ∡oȈBTr%ەr~'"ޚ7F?P{5}ihP jy#4 z.H;=gmh:rU TD0,"~E/SW;TѠ@FDNInp9ydfܲg8ZN]-o)O/ ߄·G"mC)SW5TkTmrW㒉|WGWWޤ};/c֪@4 3L*A#p&4AbJ\5'>@%H_(B8pV|yV*R9: wA`6餧{h:3BGuf ] JGߓyu)Vri{epDQؔ5Q\D(i(#(u UE!x/~ F‘RfW'=s^y啣[5hc]5T ~ؿ's4|N3xM;^e%>+n9鿳ViJ&nQ A㳺j-(rӧHC]3у4!,{`nYCB(pR(a(RVx9lE)û GaCsszUyLVR#khHuQ#z)iRwO/CZXc8'MGGWad~t$Kh 8j#ɪQ@$(ouK!" Cil(#dXGf44-W5vzVQpsiڭ~c@U?|Zu7}Nά H˨`އehiB:q@(R^D U̩A)JcEa3Jj N-g y< u8z4zYlMqUin5NKAVBaSQP+7YNgR/Mѯ0]H`SX`YՂJ%b"ɍT((,!bi'k&͉]&Ш`R GTVTžԃQYI8ZZWT64Dݡ!hЬ?ޞA*sm<%#_RZOq!!hiZe/@j?P&+ҦMљK PTOX(JϷ@hy…^Kh@כւs(.>U}M%%e4wҦށzsu|V/4VnT о-Z 61<.Ȟoi:>cThkF#VRd׈w7} |hT 6_磌*=|&g|^,-tF^4Abq7@,ҾD!󏐒KX6';wFHd zMAچ} QFUui1N>'H?}%D pY̹N&JLG w?7gǗ/-!'*xѧ«gq9#6k0e<*Zcmb1#o4񣸚rvNb#haqMj7/?Z xV:H*-k#98 Iصb`qҷPҪ=f":.9U˰OQRYw`$Dx;⡧ÄБ%xt.)?GX,2 m[L%sOlTZ""+\CGN$ I gLõ'SL6(-F6$8))7qx.>GL|pٱ1X~}{d(7 %HҫC>_/Jvvrv7R(B!u!F2^v G@rn#0yBNDВ-`{2ND%ȃ tz*{u4>]`r zWs:Lށ(qX &Z WJZiQ1I< :A19v'+.C3s $% *х'pJ>&E1M{!m2a:+vz-@cm9Qr[F 2ȩ-52rT&c]Q2=q".]D-7Tjo>YAC9'YG~qb=rZm$2`{b2m|'>uNN W/IO>N+H-b֞s|͆f(Slhn3!d0Az9?xdȓzl {b(U>5}}<*DN5Z(,\q35c#mHhq *[=i݁sN;~wppȡ{8u۹4z ,8՜QjCpTNcq3ӱw)ދZ.ȷV55sL(ً϶큃;q@#4kwb4S˹ Uh㌞֖Fl3r=&.|; |*)fC!wڜ%o~A0CpR3uPpƏI8u*V,_LT!{x [`XFDyqM*`oU[θB巉'ې<{Ņ"Tp%l8iJ3P)|9q&ڡhdNqݏ| Eh0: OS$x\&qA0'x"liS\18HGe"8nPNcD*L:BA̰$kI6W`[b?"Qy%) DJLDvR" _ǜ(YG:}DDzDU) vGu+'cmD/_G>AIԥWi׬Ywٕ<}sҚS;v09rHSB&kqZww[koϞnljR?ݑ&(gΜ1CqgC-'8J0>|XNq`HTmrx 'ǎ7f* |(\P,-4q=Y[vACUU`џRRO^{!THeeet\xr'9hK-H/ T(a6`[LQC|(`jdWC̝7w}0Ҁ^wN} ] `ѽ9ѐb}ٴ{̫m|ejkmr:I^(=7ѣJ\`셄O֟ =ڟV "Y^ !j6a~چ EQ>0Nk*glTFeN#'MzV+-a0>>ms sxQNuws8Yy-h~e!"#%,α{@{|:~nSH*K[ Քr[W m(#5Q떦Wʣ`mcz3Teݪ镝c S4avʮPY3UVUhOftFWeqUGߛ]"g(JĪU*2*@Z v(Ο2;! *ds:#u "C IDAT膋Oڭ֫gɴBst D^K(PL~@ f%5W6 y,.GRЮWiPGxMb\h!ȀB(nQL&_48wVƀvdfNr1yWM-{Yt9/&6xċ@Է\ٗ]&/g-\ T &z2|)>4c_dSަ1 !!!C7[ee6nK$;ۏ8n=_'&*e5N~PW|o2P`VG?#-tۇ"eomݦ(O.v@X' ;_OCQx| xZԼ#A+yW|t/{* U/C>ćma=џ?+Ϥq51sG={RMSIv~97: DTMa}H,uJQ *$H1Юo V%YJ(7MJCoy[A/mc0p| ,O4ND^ő|G;/_E8Tg2yƾW}M?nj:IaiΣl"+ ^N4 e&_°ש8jptfҿI$8X;=,ZlR_|;CEPזt(v%ZeʩEz d^~ofҢv?\4csp|'޷Qzսb ~qJ"44lsQjMmԾ|2&(ӄKcn33FQW(4Xfan俀8yI_4׆2*Luʊ T5pv fdB3'(cgeK_N I}KG5ᶾft~qze(@$]kAQa&(m/[Ωq޵VDlο"fiEqMu璲=Z1M3#}q3ӿB+˵wkcʣ/5r" CK!C|L/fu^b?b}}{WG,y"vʂ= 5ަj|cL_1\ʃ wA -ء/ 4nɯol؀2T5"22XjζՔ`=H6e&(QAn1GZ=qjKhk^OO7<H\Ю`:{s*mk BaM{Ѥ:$v7sshJ1puœlpckń+-u+7e FZAjeC*7Z|y6\9bD  9O5S.v&~MeYcq;1Oq9G0(DK47(/Rzd ӌ!急ɕ_Y5/Q o{n´^d*U )~P.:&OG1Rfdz 'FP G΃x#Ϣfš1eb\*A9BUu#>=[PVZ5嗠 8.iaDk[ V䱣H)E\t0< n/m[RꕫXxV?n@NxUD5'O9bϰxAgO/4>zlssh r=0-p5]s,&$)ݯDbd(N_+W.#~׎<"ܻjp72d<سF+I &JQܿ9[aA~1W^ǂ@v3%*{{'.}ީ۠mjm0vŀ_gS{M*{6{PC2y`|˔}617hÆϗFrkʳa\z#"/S n1c'G'ON/iKQݹ0^}6IȿD=]+#E _s3p)]1DDFa{uuM2wщEٓ##;%u:n+(TC@8~4CϞ~Ήںqq8a"߻eG}J:Oh'CZ_8yר P8d]nSJ ObE^CEcn9`R˗$f>k,YL_Nm@rMp8*[ tZ Onظbܹ(΍+gsQz̛?WR pqq BǡyTVoCQ!E8)7=r3BCmKaWo?,^WOє,芝p Ffa$otO*X"kU:A-qϝ\@SaYb*00U;/Q»L}8Ѧw f;G6 ,r|5i&ևg)vNED"(Jĉ4 n[f̠ir*"itLkW6DJ z{#NOIkW آTio׿ez_EVJӈ%B&ҷa_:a-{9~/؟crY.*r2cǎ /LO..^τWۅEdYD;p/ZlNQQ4CdDoˑ3.?=<ɹ@ R$s WC_*)~ߌMw'vHKKU$nE|p^ٺ@V՜sT:z fR *e?-뗵?U6DfSRqMlF_ 0U`OmiEϘJUmJ?>^d }!'zY?9E.W!_XKIH|1}͛7sj*JAcn]@p8g>rIDI!OVZpFhLּysj /93o2$MK𮄏8,c|D% FPh=-y|ap`#/FeiJ=qߪIn[Ɠ^>Xb9MBa3Ox })¢1=<y7`|ȴ,s<ѩY 6NQڊp!=K'$ 3C5.U{6 K-‚0"4`X#73|-dc==<lcY_Krڬ,Ww-sreKd<47!zTNۡ 9A_?o*(DŽSޯ)X\Lz$K׊[t?=/>\%vҊ2 A!Wҫu+[Z?WhG,^`; YWJM~9!167* cs`T+-˗a[Sx0ce2歷,\wÆ ? :8bQR\W* .6K!:"X|ZhZIhhSJ.B0uAa<5pe^(#X;';1#i&OS-ђ9޸|opÏ>5L&lcat4pIR{)VT'y? 󊟊9ˆd ^DSړgpB d``R_A7(HBb'GEG AqD5oqn⦌{ ݰl"QQ{!8:bRx**/Q+\_"8Nc= d >WN^,|&r}DȊ'uT Fl@݀pgVkrۯMgI4&^c]@)u݁"o?HLm!aeD!n :&Bpչ tyxg(ՋUM,w#ݱfI)jEf7 OOӸЎ4T(թ&3G{~*CnUHĵ P,ɵаPQ(##\%w{׈T'ʬNODyr)=YsWKةH1#9 \!Aqj+eyͰ^?(ēFp 8ʈ[[56 =8U 2u^V* Q Sn^2_32΅{xP5Z]Ѯ2WXc ۛ4vSiTUn$QIo[h&lBH8wM=jC_h4ޒw2w>fĎѦp`Ly0PuTyŪ2N@@(??8RֹTi-k,r} BXQ"ӳ~ ևǬUp,|qVL<?+%3S2Ҥ`G3jBk#w6&edSʓ|RZ#t^'9 i~1/ITƲPdK~)W I_Qg 7)H:vZXAs`L !BT> ɇkR ''G_F " E E!`Z y\F/u_YQCzz:PetVɅf~ 5u RhdbNHZQ%#*P+(c*y һ픡2#̱'LÜfLXҫ( ۉ0|XG j;'+JLO([_"P~"{q}y:tjء,қ'ȅPꪰ}("iPL4ҬonnGA34*[,֐ަj cN~ $R 4X~FʕE&詍xm0Vb-hd^Тnmmi&(yr_)MBҏ!C CN6.|m'O U>!mߊ^߇QК1ȁE2nٌ4A~‰''D! : Rց#8w@ji2$Y9\ dx\Ǖ&F9ĜHQDQlc=~vά~5V%ʔH")1g@d"s\ @ ԭ`u߾UuԽTUJG-?˞bo~(,PӖ!H^](.D .Y) J6㧊hM}X2gW@0 3pڈ[ܬp D Γsc9}8UKRFx8nGxωz\mƁawǥl"7$nZ7QUfu:oE!nEBLNWrB2t5#4. tt)֬] It:+ALH<׃\=jkkP\r\Cxy ~4-4uCi5Tsaw/|A.µΞ.NՅ刘mvM'/-0{*-.hzj}8{8Z{Nfbp:֎dX^=-[ƺ2V7q=eʰӓ뤼W8 B&E{-C|}Ict@?[Zf&Ν:݃ؤ[w̽0fO\B"想)NTz=tZV^ÅkX8*fAy:;*ZJv>$a{P ҙq갾_gs:98Ph́S3^QvuB0qiir Ν=˙y O[suϞ;9d#T|pP:K~j#baA b9,#gGz 40tFd^@^sHI[ /!u|&~E C18g\y1[q4?D'pDžڇQ[.iM"❜](ڤ,CBbRʹKhWlΝ0)s2B:dc;G߿ r7dp HTAʜMM?lM-6uN9cc/\lBO"٭ eܧ]{ hČ:'jE{{ !Eq(RԁUXY-ej--OAҍE}bb0$Aњ^igG n;w9 vr8KL0B6LC*Gqs2 TY{əC 5b" yNx%L1E^jl6(UOQޮ)A%YKU946J'k?.Du2A"qg D{EW9U4B$clA+PJ,FY:$KzMP\I#-K%5ujz.H,ziO^S_K3~{gǶ&ļ]NUzǦ0vr 'NMYQC$0hČʦ7͌YO++H9pb4ŁH|٧%d%mOs]}M@Fw r6E&t0wJ}kϋ nĞj,zJ%?#C &]o!q{!Ke;2#Uzm72H-WҋNg[鴻o.ʙ,=md8?Δ9jdJGq 3Ka޻y:}d^I4 |q5Uq1ik+ ];+H'}|:GMYP?.>6됓~6BvxQ 42:PJesJ '/fDPŅmhB۟ZI9ɣzP\TzIvqTVA_^R#[B[QXXnMoKm-E[s#r1;ەYhmm%A:R^tyy{MԑȨcd 8ĴhfcZ0䡪^αjetO[b<^RT"Um&#Yjk,~jPS$_HI2E92Pˮ(hEv/x&D=sNO'1R pE팋38jǾMjjkcD5:, ڈ×v=~!V #YSsc 3h; {GWc;݀W~x/uÅtѸg_~*|v Z7K 8}I2!" ]Mu'\:̓vh~4N=nµxm;^xj'G$m =xqE}k(,=x]O?"R_~x9[OIэ z#,Y9%=+x;/܁=mcvbOEmڴ{>x68qzXftM8}x?B?m⭷~,"Aَ[A-cF ` D}$F[}52w$Nƞ`1m(,a*Ӧ 6+sA [GLy154$Xz^G~V{wIe W`Y99v5N!x'Px- O3B(U'&addHCS#GJt*u!ϑԕ8 >wwO§XpW3.#zp[Qё@j||=]dԐtDUk/PWWU+W19;H@Y` w>B|nZ3iy&vN㟋 58jW .$nkϏ%T'Bj&o8|)!t ODD7-i4d[,[7?7{+>wuL?r;*.KYo)[WlZ_!{\{YsRS'$\ZHAnF<ц=ܜo"+iKbIZ"JrI\HJSͽNw ^Ã^jjq聯upv,Oۯͽz$Ƅ3ayE oE_kk鼇C/:#CZyҀiij騫ɓ'ԢpUPZp.'Q!NF|Bq&:،2tw:-imHxekm+:/k(,_6"dbɇb2N5:|e`k8t]#[FX4<7lBMQ./%#ZښPJ>mTsdo4׿ KkQϓ,PV]ϳ8>#075 dJue?}r5YKr6ܕk(!/;$ב87QR)!{)0hq.MA2fC% $ܶRN ̴pi]ՋqAXbvI|8CtG?c_UzRH}>Ni< ~$. op }uGxnQ,hGxIkdT́Sc;aǮ]h.B(GтޖS87L5"*NhMi [2,_~ w@#!Ytp2Or{(&1>aXC])qRqtֈU[ľ(ÂϞ(aB8?mtKWb'N|%>̴:rKH^)-I$" 4 讟;?і DH B ̎#,9 .;D>qL@…鐲Fs*M-˷}g䛷/Hc^tu*с^bhm]_"Au<Ž;S?O_=ދ<zRXP4r}lDѰ'աAY= p@NVoʩ+;3Ѩ8p&Li8F%§~rlmAϋcA}3RG+Үs\ "8I&qԟNk u^4~dqވAGVG1_:ȋR/N; h0>2^oT{TVyﮜ8k]`i`h@s fnNnbroF))oNOϧDoĨ@x\rȪƪV]j936<OAQ;L%u}cDou(QGLgeM G_me1ܬ]@"/s1\ÔC}\TjLisr3(s)(ȟ+| $yO` L9aƖD"̸^H\x]5Tk%:k=V-_Y#mO>Y J,, %|^"""M2T^^#9mt&jQ8;X"*<uuu:Yh@PoG rm,(2r)JS ?Lm q/":%AJ=r]aԁ%L C+QOz%L,Kz53$,i61{.@d:>@6| Nv b %_~eksh 48B̙-laVp *;1Fri"^#AXҸR u!qR E܈LjPʑi1}>>#k,05g`:#!'Y144T M Fw4%z!wU$D\CRoJ}> є8:J7bѯbo Fȼ%S֫zߍr]x]N8BFR xRe4Pmt9<@M J@hbLyY?+f2X p/)6XI/ؔPIZS1%lkC/Kqbw*kELS!-1 j<FTRbKA'-C%ۏf~3R&(rcSX~g`iҩ#@G-k6qQQQѣG0Oɚ^Uoc5g)'&!#/ȗ@pC,_Ayy9IFbVIݥghj٢3I+BYbJb]p+=n3Kz>>$P/8 FYS}{f{YFA+r4{Us k!AЋcuPfIl9 Z;AV nۊܼɑ|m iB㛈"@Ƣ[cjmn$? tF[xl DC?7}/_:zk q*6ZXAN<̳<_A,rpv+8]xD ׊KLv" \RׄOf䳮{yϠ%5?4[j6򥗬gX"\qy j݆e<=U];8> 3%#{KYP'79ê#sd?IAogw#Q:[߁Nl[ypgihY, h#yj8^;wbir"j+DĸP? "H9^7-Xր$tRp@XX(c">&ERBgލoEc]8mŎIOM>(U忡mQ84a mضe \KV-sp_+Νt0T_ hO_\LU#?'mVWÇOAo#Y/ ۶8Ӂ!]fOkå c>$(7lgǩ:\&㱣Bn1l\/p$DspI ז+!01y:hB[K>l\}n|>&i;5%̏ZZ[0wDtdR)F78~^Ao/I&m쌏#yaQJ P[Q+3aceMN RÒ#NAI&EE#ƛH$'Rm;G"<Ɍ_ONtI7ȩ^42o;'<68p9^dE`l^x"[w8'[ʼnѸnrGJN{IނVee1\oՄyAVf*PY ˼efs,HN_ɴP}c#\rP C~URƶIu?+WEU~9g+Et S9pLLA:@eC@Rjfc2ѮkO<_p}o\b:x$9_P^W?36>VܫWΣΨ_/*AV5ow74ב{G޸%.,ܒl8s )7}6|~\O_NCu -2H]7*,[tA+ ) ":QS_)~¯ۢ{>G^7_wns+s[s;vMCFW<|s?) __t-u[z ȓ/ 1$|`I9mH]b(˻ ȝr'; q`D IDATt(iCr)9dGSu5$% f{;ifv'C1IԹ>L=³OpI#!-z翌LND& 17l` )e͈=ݎع*Z\ %Z{G C%.GnxpH1a;D.&Vֆ8e/<ިn#+9"0н ))>Fd*h7 {(rExgv}`Kd[/³O?:>i\qA/F5H BNNȢ/g tZ\-DtJpT@PS2MHAn4%Vyf"\+æթ̸! ,cO͝^Xvln&l.milȩ ,K&n #<غȋ.RuG?BHhdMYEBHBL0e EpW'9tBtl"\ p@G@:s$ta5+RԂsHÖH:Ch;/ʉx[@l}B3ݘ0]iܿ< {>h$HքG3XeI n#ێ ~3%1HҧroZN!(1W9o/8Z쑖? ڙj0rEۊ\$%ROBp\i` ?{#l֪F߉^8]4 -/v72S[δ8|L?4Jou%i4 ̟Et3~i3u[vwl^q)ffRG`dw~xgYvM 6Nʊ@Y[~F f|HDEEblUc`"&J Y4a{b|srrF fZO.eL~|&d)ND%5@x8@FIƒy6]!)6#8-'{-J1-JRbYǒʉɜ'$Or'i%A\4]bَ,uScqT"!| (S;EY,L'H] ǧ3$Et"O'ힹՀ@̧o 9漷./bHyKbH_*H  t.e:s>1"[b f1T-i%F>iC Q?1r81 7Q륥!Hz)Kb]X ^3Oەi{쟚Fb> KMM][oA-ea޽2Iڽ&i@s &mD´6,N;ً=aJ3]U a.,)D^f!k٥|-q:L@^i_q"8Kzq"](Qb1j=6g5G*yȽbD%|SF1A.8fBiKfB%/5Ē%C)E6et%2,qJY3 ߃h`jEGF>̷4bQN<hQ@9+XjyZΜ4cs:#7?n>bg:KLBVٹ#<4ئgd\<}rZ46!>?~MxO-=DM 3vIfDh@T//򹠹ep!$pJKn _{t GYWsl~T;"8(RG."L ~\H#yU\xzr%G٣ X|DCBn-pM"_Ȍ dQ6@pfagOEHb膹VF^'ޘN6$+[aGB, $gΗ^n>WqR1Â@6!V]wMWBhՀ@m{:?U-]ܢًkV f h!p_'bDYqrPNtެZ{ϐ^`ҳJgE@B/7 ߮[HW:;{ b#D&!(|^'`1j?t ;߁ɿv#~ӟeĂn߿8QO{2v4[E>~"soΆ$䥣V XJ8Fy׮ד [?אGDڋY_?78 @Yð^7NcO~ '~b\^^ab[릱oV1'èo?r^,W̾8x Ȗúȗj;h!T۱s:mi'~ZW)# v1 +ćʵ<عcG  &`Gy%u$?>B)OE/3d .l^ÔK;FWgY`;iGsmJB)K_PmQC ф/nì!:o%#$">Ξ'Nrv' }vmtx\TOjIU։dL1Ee ٸ|m;l%,_v4'?1Y c`׈>?7*FYPVLg›OA?:X9&1e|81n ߓ]G͑p-9jՌt|ۨkGR\^ILv+xW593oհ?W{3v'$5 ۈ4=9 ;:y6a9؃s4;+47t r-Nx!2<6D"7Yȕ-& <饆2w w;i`.Ǐ}kcD C(5L5k {ꗯ#51 '^"w ;N˩D(|&),CMy!IrPC7N]}_\Q]]. K@Bx7^nB^>ŧ@߂dBIq xR^\6T2)pц7kLj̓42O䜻o:;h$皝ݼb=7| yO|Y[9cz%F ̳ґsؽh)f9Vp%xUPZC^g ~܂)(&9tpcKH:*NUW%OQF-ٻ4]@Qu<^SSRnpsupiPb, -\+#je` >doF6nKOUY3bt EOt9ܹp_uheyȺ H 3qcDDx!͸䣛y[HThJR:PpX;̑'bmS;v성[@G4wNef#p Dr)Q;ah@ye|YH(@p4jlͿ{Xӡ㥗v /v3V|˰8@¨|5 qp#-BГpٚȡ ؊E 0y9N֚pŬRH|FXa1<ěkPU#DKY+C±$-~M fkq4a6NC")&Xs}SCjGNFGX˵?ܒ lx`#)533]ki-;O-&T$ t$iA FLB8}eK [{37n$/- N …V$xF\浄WÈV: r52M6=@>v-$%+t.4ccxb] a؊Qt& qU(8r! GM2ee%#"~Z;}8:c&ć vWHAL|LL[tf5oBeK&<,HD1{Ȳī{>ºQFL-.·yâ%O0GjaUs㢰XgGAp3$G q`̡-Ԕ0#a5*(D `#X@; l[T?;'gNa6eK٤+#)gɧ;_-trU/4rnfU5B :Aʗr\9':35L?V;ܫN,xljYoʚ. t+B> M.Mm ˬ0O>m!|Q0kSd&_B$!\ F4++ Xc<أ?+..V`.e!j]&+_Izr*dG TMM {|%8vXv!ziAqRNeJ(%Җj,iNRoT>Ȯ9!t2 HJX,!.@Kʓ ; e]>MZmb68> 1 s%1.=| ~"1!Fx'm%FQ|TTb̧j?]b,\~q.]gS>6II#Ab=P. \DvU]YO-GM;X2DqR[: ˽KXʔt-H=;YזE! ,hČ B0(FB l^SEa2WDb,bPMuё?ݔ-\tOUj/# J,Db1j<̪<%Db%;XTT8caӽɡ^Sce"DMs1̉jd 4b>e( aB(%^ -lWӎU=}:mƪ*85T7Gtc;;坚42nvev.|ME6;Q~U}:?q F>n\X`hļ 2&iC%24̩u6f2Z~f́ByB҆r\|'o.EW[PtєtW(3NL#oUXۻf&w᝿ ղkџs =ZMYj@[g]oH"Vr-Kp!FR'bj9NG2Va:J(ۖnX6V$t*@YE5 QUz!p!a% hE(L~q#{{NgHB$NnH^;[Y~dg#`.U)x]'5рׯYt,# a6ǖ3G>;7P\J0[=ZpgsǺUKѷ@,JJ1:dDNem؎Ң<蝼'D# "qW9q@` #MGXiz{t7|0!.sHL'['Dnݪ}́Ss7Ű51>+=X|Pآ_Iv*،ƆfU}Itޖ6TԢDfu"v#$qx197H@۾#xG1ɉ(zwʴE7oƻ } | {=]<C{ށKd* .ǀ@sWUBe{[ut֖!j?&A4S_X:3pygo{)U9:o'J/w.R[3G/> 寐O>F$aY8 {H^6]{86]WI>щ;&S=~]š,.,@]//VXlްUOC"b/A9j0@$՘x,ŋA(wtw^Vt-H]>8Bx/:pk׬BIQ6|QSUvkƠf>|ӣ&P5ImkgA-_Lj} Yy0 Uuv des[ja K_7I<,噞IDATʙxґe뭋+\%J/ȩ:t|gÏEͷ}spr!%m0Ȁ-}ᆴ%ds":+ǰw#+~?DqE-vp'$.Q 7.MA&=O:.G6\y䩯"Z.*H)5+Dzϖ\{p4{ 6 !_/rcTy!>$MA(̨@G"u hjD ذqPą'IvBko<:P:#{?6>Xn8ý(mdz_}v֓?'Tk@s ssD+ X!pq͓Ɠ ^nZͩ']9"9-UQQ@IUQve+sE1^#prD&}J>k֭ F8DIdi2+~aʔB&b6 "mxT< tXԅ " .qo' ! psZ:<]C~h% ɔ9*92TnrnomR|#Ah%57(T!m;$XXW|ñkJl~1xH[y!N(gIO/ :_ڈ!$5 n^W75Va/o4X L-LVH !8xHcH7,FwB &w nЍ^b ^w]$X)>>A<KBaFp>m:>z _wCnuzM&g)r˺ie ljlG-XO{ByĿ{%O_K|4>>J595 ܦ&L~vo.zUOä|S!$W]oyL}R/jY7(DB4'DHR3 4\!V | A9HfZz;e^"?9;i>H$+ªCi^'ͽۀeV[K z~Ab܍E?9iɟ9SeK&W cIĒ'˲Uow#?@I{0ϣBS%/C7L WLɹx6)-! L \J$:2NkSҍh@\Mft'__; olՉe5}0ɘhPD $= L?co9`&,HORv= mM"Fݚg=ln):m߹RIAg.X숣w={yPfvt݌:3^`?z 9Њݚ[Oʣ-C<"VHXJ:.B%_aK V:2䌎 Oԏ쨨8y BiInр@nQ_mP4|҂+ƚ11"4Y2#qHIgbMtiYt1i7Ex6BI+K{N$d┆d4#;'TCj0fha #p2 µp#|G^7Q(6JQ(*EBՠΡ:P}+'w4̈́Cˠ5&h': GO/{u ÅaL1.&Sôbz1E, ˊ`.`l6[m^NbP868NgE2qGpgppqqKx</]a4| *>-NNΒΗ..2$ =^^ޖFJ_B~3C%(, ]RB#H$E:Db$1XKJ%Α$&ɉI% uI Dq*v$R&A)2L#k]1Fm;FQэ1"}Y&z& &@LZn ̼̞)OphT\cyβ˪ú-l86I6K"1o.ŗI9M|fgfWawg~#CÝc7Gm9NNuNo} Ü+\\\a\E\W^qceS븇yy yy\eUm}ɇSw ?_ߍ?S@@+AzA5BN!6!PPРwaQaGM/E"""GEEEEDD3E[E߈1QbFQ4#%$<%Hܐt̑쐜rʒj$' -}ULL̒lm mrrUr )p+8()TX8DV2SڣԮMj[Ɣfʝ+* *a**\ΪECjX5ZZڲzzF3MvM'Z$-K<mvvӥSWW7MO==tn}oc`jk0H%RmQC1WFF43F q&&Y& 妯̄B͚̖͵X0X8[xc)eӲ XYY=hjCo3fgl{vخ}}֡ὣcm'V'_N?MK'\d\R\\Y]]]YU͸gyxyzxxx꼖m?}[;Oϰo__??Ϳ#p)xyКAAM`lWHK(]Oh[),0sˎ;zùcÇ"#R#""s"'4JMkvl!܌M+o_ЖȒx?I:)/Cqr] ҵo]/RS}-;-5ENzU=={faјɐ9Ou_,߬l/9N9{s.SS\Ϻ74d?v}u"ޢ.]%%%=L8xőGV_v\TsEZŧcnW<^]ILP\[X]SC]3[Y;tBDCW]~ɰ;Oɝ>x:ڙ'g-v67hdkm\;uyC.6I4U6354_yCV֮K_|- t%B{hWo]ӽvS7Jݫ7n.DsE{~6wo[ܾsN!áԻ7ܻyzaoX~ޏߌgDRG=D`1ϰe0=@"2Dt^9LטWXvQ9(\7y8xdKEjd[%FdhEbJUV444ǵStnF &6敖T6,vIN]1;yS_4 ,2槪YG}iי_Zōer J7oy1~ʱڻV^؟A+`nx FMkB<%ޕNn~3#IٗE=;_,`0HeIM2T[ JJ۪UHj445K>2^4zfJٙ'XTY޲afjPxi xP)#Gی_Hxx3>9/%rSn4{콜q"hl\<|`>)WS^rP#YGS"*WbkkO8Vqjƙk ?[9tk- WHBW)wj]0vx+ogywJ z/~ڰCQG*cljO>=}y󋲗' &E^^ϼz{])r陙[>%I&ej~la+eߊH[Y;oPn_a?!ucO=x|;=LMr,dc S> +4k7[;e}%MfEDĎKB2ƲrM #+۸TU)jxU9IGZѫ?l0(8$JZԆdlnC>Hgw#W97wg^-۫ ||i~"k# `baGvDGF,EEEt*}'%k"]%^vϧ]KW`\̼}YbYKٽ9%~y v3E%jׇvԨLW>S1tʸ*jZt̉uO{*Z F\_hJjξxҭˏۦw0^p=F}Mdw_f A$?6Zcg_M}OFs_n[*߲|֙U̱6 $> ' /ػ@ş yl^$@0.A2 aHrbB}1H =xEAiP9F0j̓6B@;SF&&SrbͰq UF86>?ANgGGCANGL?G&mIIjc`a0LV u&L̾C,,X)٬&).{7898Cp\g-yx_*m5x!EV$Q}1zqSIR@U Ty#Ei%m3c*T/R/8K/C%~&f QVl>q[9q4"jGvo7} @70q=6Q9.V$Tlb{iʋ4+{3 d5'<;XxOiGϕ[z>8Q܇YW#7~N?pAl_#%Rh'x}Y`N+R@RPU4BCGc ,iLa)waq8'm<g{HF)J D4џx$DGffD%璗G02׳!'0Kcf̡1לw$@`ЦpAQ/1YqHej21Ar]l-̶*Xڪ{khkeiiן %LMM̞X,hm۱ٻ9T9N;$w!U}G7o$@,0(X>@͑Q)ѓ1Uq SΤr.Nߓɢ.eϫ) !bcCoV VT;y3\mViQdپj~wz9,\ [ W9:Ju2MỎ3Os_j^, D~[(e A +. =D yaE8" lc-vE[`R0X 8 Rq7'(ttHuMx@4 dH'*2F.*=>Ǭb2Æe(Sq98}p,o  y ˉ1sbY~zbR2BrJ 6J~ےKTUGV45-uOS14&6-vX>S(tEǐ9 ?N@;ڗPّJQwNOM[8ў oY-9Qy_]‘2k܎/Vޫ'j>cvy E-'/ մv]c,J3o-qbx7Ѯ1 OV)S`q!޲[#CeU& eQv"1H"|4G+wCmjGMMprekoo/vر8K$K,.ԧ>QEMp"@2 HHCӧaXLw26"D7~_\.W2\a~3ٳgW'H.$KxݻS&}%:tyxs(++SKIFc. !un޼WcX'J#$ǘd@yga Q P ] = wZ39#,QLkf[/v@ !gV1"I O}q0;Kl&&,sbT'e(sK5rݱq5ן0H&PVsUXIrL up=O`IV9*$GSfll`)S@*u $K* H6!cL.^!2:y4WcXf&`L`H0Y0"C2F`L 0t&sB9mL 0&+Da{&`L``hd5' 0&+DIwb #cSq&8ӁRM +をzQJ;&lyĪXy P*U> b&VL>&l2bV?á͛qD "oieEbO\TNKo>%@O M.Z ~Ŗ JԔ(#z f퀏Lӊf9[H0kCϣx;Y!ܝ9[(?d/|htNe  I`e"ˋ reY곗躾5%de\k B YdYP\zcJ/^Sc$YW Njj%{9'' "}PdBy܉FrMibdJQIYd}}S 9e-軈rbE$.j!EhlivMcFX3[ewXX΋ѧ >/^`'iaNI|#bPo<47]OiFe~+*A}Ls<&|c n <+B鋘PqLFohud ei,Azio7"-ZϨG7RMeGo75F -zvHzˤqU!***}:+偰:jESCӹjbG8kiGC-Zx `tXk{I*{T48ª]V3bLjW&Q[f3enصkMTյi1)6N45u4k dκZ.U¦t{Uv1%:ӠwJxT̈V^_rE^QZNuxB̴NɁZ xNo eC}Poa-nj]vmvLvP["}D9Jj4 5O??w4!ՉvÐ/cTfZ5Qǯ6"HF/Rj29FSl/ 6o'ic ȯUb[QUMt 2;T ձMTR "S"QC{\GS+eDꗚA P!íz]f xӠDS(m­+CvRDGXp©+UawB/Be1l iiR ciu4KSlAF4E୹g"GCi2ˠ ` -sB Ch Cayi 禕H[VH߯?Pgj;)"hr4GۣB&jeKYK`*|PZBok .Fy1Ӊ떭u<;_{ә, Ox,.3ĸ:fؿG{GyTJ.W[1h.Jށ4&'1׋qv'ZtF#kݟa$ l[YxtĊg3 CnҙW"n& |X_SB}8!6NlQJ9*j-cL^xh-6}o ͽIӖ}(-;QZ^Uױ,r.|>sźT;vzJ5?K(p4.[nqb;>ZN{L3Ow,mDG />"/lۑ}O򕣻蘁?d8G;6o:dǢ Q5&:*'`˞ cF.;@J"mflY6(rvv+OAT83w5UTxOJu WPnd̪^\ :tk+ܗ1%驰FI t]BG7Sߔ8;»멡n\ԅ Z$&`لLs*.Ա=xM0S/hʐ="!&A늀~5SD0Q.tQu&rǪҪ;g C-jLi <#gvP$v-tYC.K哭i&]Gmh(F司tTIs]?hW_y(Ar0M sg@Ei!5amZ }B)C6ۮ`pt4Z+JbF4NO2ֲ6WZVL!d w8PCRT!Է)MjŸQpF0y?ĚfrtZ[7Qcr9Pkc>^ك{>؈lvuDU[daSAs4}!pZy*i^?הO} hH)</8s! ,A-#DMʘ%whM\Bk!yT{(ߧ~)`|v"2?Ⱆ@ ,xŞ=(iѼR{(nlϣ ;\;Ok=_?99X˚តL@X1hۑg ^|_DWnҔڲOdٔ'Ȇ:Ճ݁RhK(spҖ,L{Y˽L;3ܻ;]Y2[l J[ګVf[KwV.~oN]+f5vc+vpk:PGE3<үoϗn]fazn5ɺIQfimqToή6/][e=vE{Nh'eSmoO,|_0h3kU:̓vސQ׮8 J,Q|s=ɴrG%T2M:`շР<=g Qr?,pEMHmVfMۚo؜ٰҮ\o4/l+]f2Z2L]fdQAD`bb>ZZZ[I6>LM ȍqG~'l&>%bĔ۷]]]q7@VVv$A\2 mܼ5"x.3Sڙ$ٰQOkSSE&.q_} dz0:=C/gDr1f@H>(^{xNOXbdY _Uqu}=i)N"!_"zruw1M;林XosoKO~?Ofr2M#dP3!T7f?2q] Dd/Y^h k*5Lހxj%%Hݸ?k@&ǖvqC _Bg~=};"FW'&R+D'"QfVXieI"G d`_*eu5d[W1`L 0&& Œ`L 0&X!J 1&` S֭[v`L 04#`.6!y4,&`L ˝׿}Ie CuC l[S!}m`L 0&T~gff_J~lVt###C%[)[^cOwHK0>o|cUz;իxG3 )T%%ᖟ.1t' d.fd&Q|dR9w>d(Gd43gϞeYP$?|,R| ~XN%Yx׾u=o`L } )P2&`L u-g-SB$!VR9Y6&`L`>r>TrNc*Drh)7_dl`L 0T$ Gn޼V#Dh*DS ≀2&`L`9;<_Qϙ`L 0&R"G1^]!ZJ0`L 03(EB4Wl`L 0@˜F&`L *VL 0&Z Qs;18ba>1 'u4t{SW%^Ias.4mbPRߗI䄭b\GWq楖%-?h8 7U"phf|'iR-g[&dYH4pW[ӷMdw:F2:L Ge`$!vG~)oAra $3lczH/wgV!ʏ/*#`#yMi9c9XlpcʏI>1L-2hvjjEh4ު! o 9ȲCKڈoEt]߂ƚXpW},Ygr,(.=b%d)±Vq+ƅ9ZEG}=Ҝz 辁 (XTDQi#N41Pz{ɨ,Tp2>ũ뜲I ]D91"vSy}Q âr4tE t#OS-_Z2DAC;HP\\JPBEE/0 c8]RD'1B Aq3S)Tt=է2$G9Q,4_P4T(͖uE(?}K̷jʣ#Js1Ql픬OhJW$季&@{F\իWEGG0n%8*OXN\^W 1#?-<  WSp:ڵI$IR#ؽ{яQt.wYSψ2%^%Dm͔}jb׮6QU&Ťp4&:Ѭw&U8jM7pV V{G_&vnMqѣ6ڥ-c=*cFˮMV]ssG "4LAy ˮͥ3Q[ҡ_;ۨlhgu9"}= Z9GNZv2ܢL+ZY7S? :=Xz kpS颶hTBsh*Pd .jejsSwN8Lfǥq:ERF~9 ?ܪwe= *!N469,ܺ2dm'EDx7(]v'"T#VgJ1+0F፭GS4:h+F4E-xgIBh=UfuklVveIqvȲm~a(F^bѭY!'.XGUrdOjVū;$z161D<9{d3әd?4bT{soҴe$JNl;pu6 $\grNU <@K0:@R؊^dji3?u3G߱ bUN`: |9?&k1=:ޱ1|_1;fHh,qDƇ IDATS]fc0;@J"mflY6(rvv+OAT8Tw5UTxOJu WPnd̪^\ :tk+磤gGa]U^Z}m(0?z0sѹbJ`eUcU躄saoh);q2wwSCݸx H6M J;ܵU]c{\Ya_Д!{-{1J#EBLj(`\^H GTaUM=ouR(a?v| A,tTEs]?hW_yh~VG妑JvUD%9-gMYs[1Ȓ& BC8OM(xȺ@)^wȻSxKQHSߦpn7): v.Fykko) ׳DmE` ~sGqdAZUqWtיlvuDU[daSAs4WqF"T0P-}BUu4FOJṌ|˝ƙ Qxef<$u<|"cvm??m%Tv/)cSy7<𽯢ZՔ}J!집"g."S VhuTο*Q<1[ohvY::+jXVL!vH' ikEtu\@&M9-9M7# NV uKOgFK-іk[oQ-SYx=(iD/5Kr.ȆO=HOLK}^qil׆oKwNT7^BwW Q -%pݯZSBc;OdZeWcbo<5AM kpid8␇/VBϙkXߍ[<@rl5u#ޠ"{ 8{ Ķzg6]Wmq+;zyIGcm'!JZq1ʢ{Pzz{ի2Ȟ`H^8 s)ѳ%0wZ}=g[:T|6Nߝ|u4m3d&Xܻd3m[i˽SN(̦J߮nnmvje2EJ]+؎]8vc+vpk:PGE3<үoϗn]fazn5ɺIQfimqToή6/gMD=vm{Nh'eS2m7)Z9bE?nl&00|-aBt!MmTxCܨeh)u4]q:# Q|ߺJnHl{ ?){NhV&uŸ0 GK訯V˗RdC$o,V(/;QTH)8M,^^2*> LOq*=}:,bRr}QN ;rȮT*ZaQ9[Q^EȰkM72Ě1eY-C-߈̏uE%i$O5NikVkbRm[Uhjrh;*u&G]8kMgL+=n#/aL7鋸{鱋1eRۦdCzor .v9 2 %cVAYJ+ILblj&v9ӣ/ uy½[Ocb|C}{owEwnd=L *LݱEfw:|q`3GLQGZǡS7KAyX7\ƅW]O~z\ێzdy'IB%Μzؖ1WN\&/N<]hq̾7yCN|ޤi>b\~B/-23W.ZÏ'Zqw.`QV rNK-޾K8%;*038nP6F߿ m~kE=P;_ lHiMǴ6]~"bU_ؽ%g@}(]W {e} (VvpfT?[ϣICI jڍYk8t"LWyNugBgZȂagRca1"!%J^7G*p@# M'qc{SAxTpEǡ1E) ܡM~2ľ<;|"E[ס*6[? lj3|0n}OZXp?O>e1rPaߩ98%`Fgo;j(NǑ;V5U6Ma?v| A,gF",s8|]S8Wxs^2'y/tufզрqS?!VꝬBdVqp?p!xapuo*l^ƄaIG~/^*D6siCY}w1ʿ4XL]ֆo]~;!?/.AKő ۍ)+\ LvU[daSps9l̊IOE dka&{5 _Sf>*Xغ[Cd"\ GN(sD oܓ=9NxU#‡<>g%UUvj_۷ /;&&2ݚe@{ع6$Gjp?cM!\rO?VkA$b Ѷ#P7d!mISj>mNR?=DvKOgF%5f(іEt'Ź|N?0SҚF R#N$lԃ4˴7ԇo8qb/]8Ɛc'磗f߉KjA=jLp҂W*]x%* |k1^*n?tנ55t,#jCX! f?g>=N-g˱Rki&yrdpbzmBFQmq+;zyI*<]2b֚J}pp`!fMV`E}|mIz} CcO^rf3Nnz4dl3u䆦6+?.u; +h&`j_]f2ޙݩo'vVfSP^oWW;hsK7 .~oNAY~rҷTI؊ܚni O8ɍ,ܭ&Y7)9"w9ʌ491 ;+kh۽N}vEa Ү'eSᅲmִ?$Z9lr%ix a;Αgw5ܦV|FbCVQGCwRm2{rc ϡiX=-1%Ը+E_ 7@VVv*A\2 mܼ5"x/3Sڙ$ٰQOkSSEf!9"՗z<x`j 3rM4-c裏^ t!,FUFgߣ7I2KDA. K ͹%(/|O~7PCJq?mpmŷWٹړD {6CUрSdbU~s5}Og }GbۍnV?8 p=஻w[R#Z du񊐝G7^O+>˙m00K >r "Y2hzzf9~R2D7otJN}`L 05F@*7+RU~"%G>я*H*H )S+TrL*CRٱ:M $M 0&0Tb(IPlf H]BJ눂R,L!bI; 0&J뮻\E3R!2"V*G/cڌ 0&@j0Fy֯_KGS eHJ<1PeT. 0&;ErM!Z bF#C12`L 0I t$<R!Q&`L Cxh[&`L - BʉbL 0&! Q3o4O+l!luH9Hv/xZ*D@Ee$|L|dS2"EJx$/Y!/W*E1sGe^ -˯/VCo Qs4RFYjʕ,_\b`BB}y1o6G,P}6@u]׷zC^9@di˲$"<( ZIǑcJv,cD \@ r'JtB`+KF%gi)N/^ELJN`"ʉ|hٕw%,*d5ݦ|b͘ob! z~2ށFbUTbW:/*FBs-!,25kpOb@q3S)Tt=է2$G$57trcB@vZ͢wrr[auK{#Ы] g7JՁ"u-nXYCK5-Mu>*K/bP$ {F\իWEGG0n%8*WXN\^W 1#?-<  WSp:ڵI$IR#ؽ{яQt.wYSψ2%^%Dm͔}jb׮6QU&Ťp4&:Ѭw&U8jM7pV V{G_&vnMqѣ6ڥ-c=*cFˮMV]ssG "4LAy ˮͥ3Q[ҡ_;ۨlhg׮]1Ƹ\ΦE, 67q!f[M>Q%xgV`.^7dSvN{VG *ŹLxQx˵c=$z kpSvh״vE/KQ).vʛ̈VoXv2EޮHħ/}KG?xw uiZ/YRfZc*kRbM+R;5\IhJ]낛mNpc ȯ7 FPu6iv~\&;xր=c=*E0*Edǥq:E1v-lC!S/gA%$܍KUMbxTWJ=*jU^;2-7Urhotz!RK5 9ѵZ#m/ڪ Me<Ŧ*fUB>!ƅ!㲊Ѡ=kj-(D,BcA te+2q]u|Wޟtfw=H˝(-/wmzi9I>b]VOb^4ʚQ[/Cy _ "|Lw{ctАh_c;}O_]kY-@68jmxaێ\3rTa>5@޿Z3D@[nOlujQs( }겧Aؽ%g@}(+{e}۠Q<RXg@' WQyT>i( =7\AS1{q+pDЭLlΠtԭ픝&o7̊'*TvU_-uWR'K]Gmh(F司ZK O}Y=8caRlp]8hh)%+Hh4a//Ӛߪf&L) =8Vo+3G$l* BdVqp?p!xB d[W/G~/^ uk Eg(s8O#ҼrbM39w-ez(ޱ ytƇWޠo77⮥);|d&hD1ۈ1t?YAfkث2w W")2>o@^~>/wg/Dᑖr#cĶ"G[ >m+6U_|KDah *#/\7_Ԧ`'h.1ج8\Myy{#mpҦF\R](nlϣق ymN?DvKOgFO-іEt'Ź|N?0SҚF R#N$k zJߠR>WzYo8q.vmڏfK4Du%tw|5Uhx&hKjvviҋOVx|S6xCw kPSC\ѢNB8\3"nR:aʣ-Z"Mz*đ?oPO~g/vV,Ԧm1reG// D]yWOTv ?gl . '6SǴ%OA VvQ#6>5:d~wxgh )EiܝvrmGݭoe6-Uvuݟ%; ?ƷLf:+"fnlnMh7Ҵhv^@[-FeIߡgV,eFXZkc+h+w a=v.{.X6BˉLvj;;Z9ea&}ΆzZBFv.u7q EK`= e<05z9&1B"|Gksx#bGA^O$Madt { iy6$=~|>^:O~O~2K`z>dЋL7CS&PwJspW߈aʤ駟K/2X^&8 DdɯY^CBYd[v mdt >m{ ~B5G?o^.mj^?ejEfI`hpΘ3Qs "Y2h[YOV&ܚ%B<h\7򗙋PWP 0Xm O `L 0E`h`L 0&>X!J0&`$ "7&`L } r%y4'&`L`){oTNW(&t)/`L 0&>@R&aeB ڵk())Q׆}dm) 7næge-\V!̋%UC(9.˟3y099iJԑO=ǘd6~<NLȨe˖e)6[f/^\=/~XDK%YxÕ2r/ĺo%'q~I攙ʣ`&+YfRŤ,„`L ,@ t4B$GO&4]i '`t`L $%"T TB QeSjJHjKX*tmM(O;s&`L )DRPj鞙>&` t " QN{QX8L&`k#tIɄ[%8L 0&R@: B Y&`L VV:`L VR*;X&`L`%BDމ1Mc##NKib +をzQJ;/mX xXBH*j.%JJKf<Z QrًyˈY% 6oz&r쿥uw4 qMS9Iw-yk-'ӞP^xqӟ`95H %")/|QF>n|g hV4C-B]CKiz#iCz;s P~|Q%'_Ẽo?#11,W*E1sGe^ -˯/VCo Qs4RFYjʕ,_\b`BB}y1rraC,P}6@u]׷,c-t_:Kـ, KO{,BX#kp|9qa@>xQ_dϡ4dC$o,V(/;QTڨ8M,^^2*> LOq*=}:,bRr}QN T^eVbXTƖz<#˔V{hhje6SvJ]DU]mӘتDS;^GޙPNVᬫ59jY[%l:KgZq eq4GO_ݣGFhL].6%{4KSXmvaaa- UZpLfckU0̙&0,6FxDFl.J~lI]vr9 ,Tƅl5:GuZ][zݐyO9qkZ) 3(2Ivc`D_r.׎3#D gHm&eMmU],vFBQ*oZK0#Zzau-vez"j/GwQEiGQVFkI5J6KFd"pEk$eG)Iv> nvjy;IR1+D*"7UBADٙEsZhRjf\èܦBd:z`9 ?ltLe?{ouH58 n]bdbjƱwkvn,Ɖ$M ^ 0<;#wŝ6gd`&tHῩi[.ZJ+%;{›>s|{uxg7p7ByM(U, a )U]4v'"T-͡ r0J hR.E]2*U]mf6ŏO*DFedUWJRl8P{jZm] 0~iE)D жOQeRZ\A{oY[A!e9+?Y Rb ׌YH5ak} f{L kx~d.S GobIMwIN[{ޙHoUU{01>>MwЬ:2j}*m{A#@NRL:C!ԐQp:Ĝi~ b?mi#5vꚪ(xOߍ{qI걭?z8[3!M gNA[> xTL{nE6 |6M[4o&fف+g[/rc5h7dZ{? '#4x vPUO520dj3xCdL#, x?&q ol \-~ Z3D%֬p Rw`f FQDIo֧.{Dl=;!IeePAS2w { %I[AwwPveG5%˰=i5܋2VOWn↭wG  IDATP̹QlN;g(a7GKq(S)8k,PE!! Ry<~>`ǽn!oVcPU+bռ)XF/^r>gT"o{Z:8OaOΩiR9; /ƋZPF/>ZUe̕iޤEu)D'^ٻxfCS >ɍuc ,WjRYn|LA-I`eUS7p=t1tmvc}xwnk10nFimJ 4~&đ*|W{"g629PW UQXD}A?eo \ 5<|Ndzt2<;yq>#|0+(&l);4K!xl? [OsS4ZN|߈ d_gAyr-Ŷ"gs!ǿB͆6o .äp_SM7bs&X-C@UP:O`Ϟc$15沸b-ENxbOm~Or-=_6¬|1 *^j5 ǔtM8&b QGshk%twB9Y& o8uNgKBO-іt|tCmt'c{>];PT]AˬWD̯3j C.uim㫿{,h86t7d>Ua{:h3|vVRZ=/\sydo;QQQj:Tm4F:RVhQukm ik&rCGh>[=)4 _^Fu(GR./qE]Spv)-ڄ>tPߛw4 4>fQ. ۱+ᡗr8h[lB`ݮo%ִ;#:^|rcEGZ_˨798ՑM)47'A2+@ss3^|Evk&&H EM6ҴD};P)JNIRW01:k4CfM["M>eaIIbj=6IrqnRҐI(r_[8`{ .q3ME!>#xClb1#Ȳ#醔"i>7F]KDB.@y6$=a#{>^</,|T]^d.2Vc \\bʤ'/2X^&8LX)=!MO!{mHK ;`6n0Pۧշ]J[GOa3>)vm"ukϴeSYo Rb jIhAIT3Ţc6 %edFLhYᅟ$b$|<hL7򗔆,m?(Ыǐ_cf2;(|ɶƆDMپ2ZQR} oD6 {~Xʚ-w->P>DLU.l)E\2e?qk6y˔0/A%͌~z2+$J@*Dk5gI}޽~li<~mI` !zi܏my1@ʋ(%$UE@*D*'lRzv4Y!CU E/qͺU/- Hq'b$ H$ H $ $I@$I` KI@$I@X -& H$ Hk\)v(S" H$ H%G3i/e'؊vxVʿ14hJ$ H@`{7L_R`J{>{[53kgM9J/Owcll#)I|AqN&vd6¾G"ɒ< _NY+W`q?F\I@ tԇ})<ң 0ӺgD[Sz4ŇUt4|,niYط^y%gE(\IDG-DɟDa2$I-Shj<UkڞI%I@$I`/SiT >1)DZ"s|H$ HZ%pMzSW9? ֬rK$ HO-fJ;fBWB6UM-?R$ H$ 6lQ5Stٚ2cK<$I@$I@B#vh3NKQ0jMLH$ Hk@ %BՔv]!bBkΫ)!RVI@$I@J L!Zh ҟ$ H$ HTVsI%I@$I .B2I@$I@XB;|ń7u:X_D M\ׇ"z7[*$V< Pڒ"C3p$ "/nƖͯ jklق/[ ShB=~z(C4.//oJ!bKG7H>v;#?nx򴢙iDVt5yHv/xIΤ*DɈ8")4i<Ya>L)Z1S~NY<+Y 8!Z|zY֢$0+Q[Tlܘd:Tm#*ATum3 aLNƹ>6@υj3&HM6"$z"_:7ZHH] Nj .{*955%&}k4{)CnQ=N412Rz{ƨ,xp,~ΩԪIf _B 1L&vSIm 774 GwÂ1窢ywX"??_̹O|fϯ`X;Ӆdd?/߈87=?՛,(M%A~oĞǺBv4r,!XX~&/<2GlrQr&x-aZ8YHyYxEz]~}%4ՕZvyE+NCjSvNmvãV#ʙ#Jj5;/kŸ7(Pv`/cr+Dv(ōxLQ =S.hdQRS̳vZ=GTE*(J`T:x+owSJJ!Wd[BDWl1*>qҘh}.80RYDؚBd iQ;b_HijJHPfm]_i-9Bq;y|SD"k^)kqL-ZQ-o!|YkO<-5cpMZ£3;h ~隧4<^Ķ=YKnr1nbI;ԉ{`<;s5絪^bP_~&'1߇qnUZ#SOem/h(}IczW%>}Xo<: N]ǟ3ͯD' 0md;^}>_]SQ[x/N9I=Ggw2r̩0hGaVv*<߹~ύ&>Ϧݠi~0F,;;p v녞Yt,=b]cyRMExKpٚA"`I; 8ӄS#ioyy-V|? ٰ8M +2d2\Y~ZQ3q_r M \hySݕ=|'B$77]5gOIǎ(y4;>=XG]y[O~HK˩YJ_J=_\HKW~9K<2qDHY x&J8TAey{p̰dhޔ=R<Y4#0w.RAkZ~x"GeMs/ϒ#b Q͡qEr`+>MM8YN6ԩ,:-yelF[ͼFq:ϡ=e(G zqeVtUm^(*\AۅbCW7k86t7dZtpbW"4]X珖cb_X'vTt.F56:Y 8M_3m|7ms<@)w bM^Fu(GR./q㣑Y[ZFYHz=(:y}}=|lQzp0$ф(CZfb5w0f4%ȩ5׆n0i)@ft]Ti/k$ 0ejZO[]bWP2Җru\lW;5q?Ƕˌm'Rbs2 w1\]fb-,!. Н]A[mmVʪ7z&h.9!Q;kgeK _KZ9RkmBrUy0n׷k2[kA 2ŋr2E+c,ml *Xlg4G!;JC/Q2[ &PF3*k*I3Ѯ\oօ/k+]f,R$ s2[ "6?܌_|ZÇI>81iQM6ҴD};P)JNIRW01:k4CfM["M>uIIbj=6IrqnRҐI(r_[8`{ .q3ME!>#xClb1#Ȳꆔ"i>7F]KDB.@y6$=a#{[</r,M TtFH@,nT3G[o-<^ vc_" Sz,XbhC*_N0s,Q=JbP1Ht<^%uƑBkf& $\NbA1edFLh _8c[z'E+Xf##q9Kd\ߨ_R2CCyn쀧&ۢ-Xf \CO@K__VehI,!ڠIԱxW.e-ܡA #yY]t˥%E(OuS)6q%F+L!Z#$WY2{ cL8VYZk@}BW ~]⾝k'2%$xBxy,%gaM-Ke$@J&%[VW#K)$ D`z)yұ$ H$ H I@$I@XB3X&O$I@'[n~҅$ H$ HkZSY3H$ H`/wf>>%e CuM t[]! :J%I@$I@F)? SSS> +L`0Zd IDAT[ٛu,y3ڧ;***xR9 r o&N`dd[lI$KB!!}s="N;3nO}s[x2 I`-`߿JL\rIS-GXB/}K-v Yb$R-a Z"jeMwt][%cƍ{jLjBiQ9)e$I@$I`.ڇ"F؈QSgaDޓ$I@$I ?l_;tk>uH|$ H$ ,6"Ħ̘ḆB<1HSb D$I@$I` 0%xcSg OB%$4?, H$ HJ` bXu]!ZL[$I@$HL,JE(%I@$I@UHViNI@$I@TfE#oH$ HB@*DKމ1cQLx1I\Ek=ĕq^|}(ݗf{߼N׬bPH kleV1YGWq%R!Zfl VF}KK&r)4!rFqy(%w_m"Kyo"LGu10v;#?nxވԭ¼aGaHv!]D0/w&MW!܏oNF"hrG˃o-l<2Έ܅FG$ewVW,JsP۩Ys-+Q[Tlܘd:Tm#*ATum3 aLNƹ>6@υj3&HM6"$z":7ZHH] lċ .{*955%&}k4{)CnQ=N412Rz{ƨ,xp,~ΩԪIf _B 1L&vSImQ 77t GwÂ1ywX"??_̹O|zh~5z1.%%yFdy@i*v|#,?9c " nRsK.rK*Kf\ wEĜ[h5 d!QiZZ)VK'JW$季UMЈrC)--n-٥Ԙ]J]U1rW)#SHS1d)W[Y=NikY)iU<ʤ0 &LIw*I7Re+W*Kew:4ĿmXN_#GD؈KǢq)v[+}.@cb2[~V' â\ɘoPR.ʆwRw75P[|a4A S<`eln)R6[ҡ^WRَE։r +NvJiRɪX-(&zM׸)gh^18!DSGeHm7a*Vnکm6޶P\: x 孷R~(͛7qȏV\617Rǯ㭢3Fm"Tw%PG#$Ү`Ԃ_9jGW`S54xmVb['NޱZN!*)\)o }\1O]p4U)\a։5?Ңvźg5p'ByM(UIaG<‚RTi NE,ZMU5ca|[ϥ] ŶeHWq El3)~|YzަrljveIQm+" *"Kˋ^Bbnk'[Gr=X8yS)DUG+bi[H^ڻb%"E1 Fzp͘$\֧ nd@~) 9w~lO<\smN2ub"+`\|Ezx*W,ZD߃I albۅfչylT=Sl JFvظO2S'LlLYÎWWTECxnދSNRm )oe8s5 mƫ `Ew8s/Oi7hڲ x;61\>zg3KgXg9y,?zDzfmk+wR؂k^$l4LvLNŽQ;1X~d8`ǽn!oVcPU+bQռ)XF/\r>E}݂~{bTNu ZqxZ;wt0XD}M 正X(<qD8R/]VvQ5==\[e6t {C;u ։ӱ>vR[7&hl"fv`3v8l`]c{:[Yq{^ʐņhHQ&A늀~5SD0s\^Uq7>L\乼-UJ',!p8pj b%Btv7/ɄpXrϯ.!d9yz:2hFː .\G\G00Ԁ(ࣥk3/&Ǭ4&*# QyN^! 8p,WZ΃Gx4m?/tO=;_'0WeF*2d2\UD%R9-g]Ys%o"c @/F9 p_ Q&L@֩^awxX T!vܳc|<hM#UU-'Eld!0ڍsL?H]Q+;QF|c9nb2˂oމCςNT. a~z{kf)rؐeIl);e4d2By}<\ߟ)D0z O'>oDzF2/̓3 p,xHܸ<|"cRǏi8K\yt5~9K(}\Mאq'Ɇ:ՃEӁ%hK7(N9TіAw2ӵ EDO[=N˴Ϩm2@?TRw.F: ?/_uX#; ԍ>b[#*Qr]^G#3Y[ۖ@;DI<>!}Ҫ,܃_G' CG/]YQZ|'k9m~R]b Zm|utfȭ0m-r2cN Fl-.u;Qfq[uAiK{]]lW;5q?ǶˌmRd_I?~Y)׶#l]ۊܚn$TVuY'u[hrD2ka 9-(wA ڲml{6 Dԫn7Fsw 4Y{}@h|<,]bn>S4|E9u&5'0!EuP丿)p_6\)z9f2B"|G믇bG,a e!|y )w!=E4I}n&0ޅ4]]=;$=a#{[<~T T4#;:0U#>CFlHm}Li3/[L]ǎj=܃t~'?YZQ_gk`)VajkHK@ J =T)<-#3b@k51$br> 4&O UKJCfMFCCyčK*;+'TL֬˶o#z -VR oH6gZL]+d:b# QlK!ydl퇒E(Ot:VF<(ԑ4K$BW2{{:.Kƽ6 -t7ޅ/1j6,S% HTVW~Mڤ,\i%CZdb_"=urJXmK2$ H$ HQ Q3I@$I@XBvVL$I@$ (AIg$ H$v 苪׭[cv^ɖ)$I@$JsZ}~:+D9˳$ H$ HH)Am7o>Oh>|mq{v/T9?G3tᅬ{{` ִDLrĠ]r۷oQJ&<W$,U.狇1z*nݺ&-KG|ӟ[c.>7Zy_k -[Ч';4 fgvǎ"g7G\Uo!2[ [ ~"{"2|;S̱VoI%h<|!j|)TUkJQ4J7$ H$ &c7nIlvL{"iCNՔ@)$ H$ H` џ' #DL / H$ Hj#"/4 F؂#yH$ H$|\b3kba dJs$I@$I@M! ]?8M!Ҕ@G$I@$F`%BB i$2I@$I@"6JĎPGWbe$I@$I@T$H$ H& 52q$ H$ ECincsۇQLx."ڵXo ͩElK4ObUH kO6)$0|gJ[T,ǽ;[6U"Q@;RIo Mȸ.A\ 3u ]Aznjd(evfmLJ7H>v;#?nxx;6"}`ܼ> ,Kiz}&orgt"d$&7LG˃o-l<2Έ˔FG$e댕˰:=y_X ƹ2H\o+Q[Tlܘd:Tm#*ATum3 aLNƹ>6@υj3&HM6"$z"dNW&X yqaxQ[eO4&#$or&p/ey-'הډ>&FFJ/sV9ZS21)<ٌK(!Ďq*;fb[Z0<`aWnX=x~V4Sг`\竿B97biv8O˯`X;Ӆdd?/߈87=?՛,(M%A~oĞǺBv4r,!XX~&/<2GlrQr&x-aZ8YHyYxEz];\Ko.֊) (@VFh Es";^jQ9m8Le84B~_ʕ+JGGRZZZK1A!djSj uyˈxzzhEi(:+wX{cRe5kK2DjݻW3}*bR..RGb.R56*b.{b&R^ӪxIaL5Jc#^kHT('RUc9 nVUth1 ۮ6̝GNKER5-V.\<*d( OE,1-ޠ,~\ {nj36*("v_9iByʮ.(SNXm&;Cj/>v2E:[';P8yL)-Uj}5YáŢkMUWF~)u"lM!2g 踩QD<1;mrD44$N;ܐR6fMawB/Beljםh &W  Er.](U-CzU(f{XctiVuJoS^U\5q2(IiqMayR;9G^q"aZ*DqŹdu.(U\£[WP2hQ^""f;}IES,2M!2(Š ,y)1ۅk,$ᚰ>Gwgpw@&5OixΉmc{f)bow^yw/*PjUyba(5e':LObcx.4εcGa$e^ЈP0T/,}ߐy5u?1g_5O,g`rv|* w^rzlkǏeH1}(ÙSaЖl0^$/Uxp-:s1}M|MAӖ|x;61\>zg3KgXksXTS?\fmk2f4`I;i'4a!Hj=s^h__Ev=V :_<36"Gǝ<~ |'!N12֧.4`F΀̝ɤ Ȳ7[BEN܁|)h&iwݿ>o@QMI2lOaZj/~Rʠ[=]q{B #4}QlN͑O%B/.RD C8R+TQ`£4Ow<~>`ǽn!oVcPU+bռ)XF/^r> -Z!Z* R9=23PXjʼn h}v\cto%z!ԿcqkD%Oq;a{(3"o~U:E`eUSm voT_کoNݵ{ں1xV8MfvS8ᰕuleuyI(C{'E+"PNqyTj&]>]ع-95n~NWKW&Yh'î7/ɄpXrϯ. Fɫ|DDՃԑA3Z\wb?:24ͬkQG'/NQǬ4c IDAT&*# [yăsalF0\9ҴV;7?Pn$x[A*sgOdَˇg2T\itHMaoh˰AVS)| /~V 5ɟ DkvǍWx?A"qn7r= J:vG/F0Ě8RUj޵x;h71 u"GqXD}jt|zb9nb2ЂoމCς,NT. a~z{k>)rؐeIl);e4d2By}<\ߟ)D0- O'>oDzF2/̓3 pRx<|fxVFVf>~HK˩YJ_J=_a_HKW~9K<2qDHY ED*كt./MSпa}aDz߰ja3}eX'6+e~L?4_Bwm,[iP8YN6ԩ,:-ye4ڒ`mM6yU%~Нt,CQu=V/S' 3j;=2-3jPU݃ B] W7ǘiQGHOA' C%qS 0Tt 5 [YG覾$?`Qa]_cΓ^F'?#N/Zh˶{wj5bk=mwtŎ2Cʬ J[Vfs]n$\.3EjR0kیU{ޮmnMjh7Gi OVG[̍,CO߭X4r9"wY[SxCwvmٶy횶݇lU.x\G> 4>fQ. b7)zI+SJ{MLrUy0n׷k2[ksg9wC18V[r vR4UEMݖ&gQr r]GZE;kb9&/믿uФNݪ6j]f/9£Q>vcN.uEl11 @o111>x٭%>|#3(N `C*MKDi)ٷo_6_zMn%$N5!3ߦ-Yi&Һ$1$OݸIMk 7 )iHQ'GBcpy匛iZ.  G믿bq>,DF6Ňgƨk{]H!{vuCf7l`sos㗿%;140P! S8b#n$nɀVc㘢:x *b@JZfn>7%S)w ?ӒEGZ$ Պcǎ{g>|S'?Е+%j_gk)!ʵWZP.sLHȌf1xL"x'E+Xf#ǂBFUҐESЫǐ_cq#쀧 +>fo#x˪ '}2!$1w󙄌y0Gǧ#k'%["WyMD`5$:aY.$I`vR!͚scow[t2eVvƻ};#FWWYb$HhUeWMJ&_:dHk@J&%[VfI@N`ŶOEH$ H$2B2e$ H$@B@!E$I@$! .c$I@$"0M!ZnO$ H$ EPBu]!b{qTb4I$ Hqn6 wmg7WU 1|>n9Fs2.$K(4kLاxr+9qFyMNN[6# $8ۍU9`6a?y$23;)DLnw}-/!ooQ ~PcH'E}Ls\LO>$^~!J+J7oYl5e()) ))-`NnٰaWBӠ+Dz=O|-N J"r |I@XC؈wߍS)'6md)Awqþ~"mJ`fz5$V( H$ HKGh4⮻_g^- kdg-bK]!b>H-]Z!'H̖$[S re6L~&60%yWC$ H$ nOlj:~LnMAU!ҦSjb)$ H$:"6M;&)@`E`ך[?] UBgI@$I@`hG۶L[")Fk)2-$ H$x&Ň!LSV^$)$ H$ HK@*D[& H$ H H@*D )R$I@$I@X^R!ZBމ1c1^ڄ7u:X_D M\ׇ"ڕ`T:6Zmj$Ņ"e"2@@*DK^܌-_A*(ز_]23`M3%Ca.Ar7o˴Ǖ73^5X-H !"h #a>w.O+Fn%6!ٽf7&y;7'#<`#9 ›&ҟg11Ǘ)E 9fʏI>1<ʯ/VCoK-A@mfh4Ռ +Vd銂-de"0jKL߸1u8&FT|ȥfWHo&FFJ/sV9ZS21)<ٌK(!썢SIm^񇛉an kQ]]ߣa^U]J]U1rW)#SHS1d)W[Y=NikY)iU<ʤ0 &LIw*I7Re+W*Kew:4ĿmXN_#GD؈KǢq)v[+}.@cb2[~V' â\ɘoPR.ʆwRw75P[|a4A S<`eln)R6[ҡ^WRَ]vEr9(u,TejEcrq!ƻjD]CWu=e[(cH)看R7ZQoGq屩=$#JUHm&LŊL;Z&,vCvʛh *0Yá,v5D"io)?ϕ\W`:}kMa_r+Dv蘊yȅo)7j'ˢdP,J*ީg$5=Gj_*jT ymVܯ4*pwSJJ!Wd㊍5Z"ҥ2GS2v N)Dc*TB698TebkW;(x7T ty@؝ЋPY46k(+DAK!lwUPP4WPTvQޚ?> YV]w\*Ke1h,Z_8պC6SNU٥2hTf@퍪jyhuZ.#p=E*&^B>EWVIiqUenHtiz\{W,~Ah1HokݝA,O<:'Ώɚ]" DLؿ#ؽ3_QR=7Ձ);`b|}vYue=2|T&)F,҇:6v'|aӇuC!u9A~b9Fk5UQ┓c[;~p~g.C(GΜz |dje'yƒ;l+ll Ci4ގM̲W`^L)ek8-j(nAn{G/5LD5NG1iTYZ֟r+ w-k2dd $`ȆfޙSGX ~LVD>[/ fBxKϭY)*(Z\5#mV3O]4ق[C{2wC&wdٛ n"d@>KO|ʎjJuua{b7 jm?)EAeЭT [=hs+S,ص-\ٲ&whm4Q/toP 2/S'qֆY7]B CNLy|{8CެǠQ;6#W*k֫yS<_|"E[ -πDtpžSӤrv^*& ^}&ʮ+ӼI$SNw |U}~}AYԆ-$[.fozb7/ 7X'N=܃KmPb 7 ?`܌Ly\gAiZ. rIu81pbPCaguEw`x!N )+p6NR\y+lH%Wp^ƐkpE$4U{g~[$)^?Y€srcmefDZJ+S~77_/JW7fΐnrx,P׉cvnܳc|<hM#UU-'Eld!0ڍsL?>^ى7>=1ބ;&kx,>,hDexw|p#|L]/`G`VP9LZRvh4 |)C(3 {| h+<п`4ܛ4ZPGmEB|-; G6 3l*" L\יN@ vq8 Gxk1zK5ՆoGO%22j[{pff~Th=yy=塬(޼ILU=Z`|zt|w VOw 0ZT  GZ pG+sj^o \ Oyϑm(>ڌZVf$YRQ2_i9Ü\(̦d^jي^eܬ4LW OJyfV&M=@Y#9rߴ+Ȥ_ ̇agZ1F!YA K{eSVPޖ]lį0a ]ͥgaZyʆw9ens`pNjmUWxՁw UR6_C'&آǮh}(ߘRD6є˪ϼ;l|pj Z45_rIŮSG|VQ54׭r^I3ɹ-+eŬw鍝}FGGqe477W^Kav. wƮShZ"؛-[cN Г4h:qctWh,VKt,,1QOI[iՍqXSlP乧vz kLJ1䜤3i 3+gyoיc.ae>|EG+Sq mᗈ@M>upi|p>?> LB/2]LPw SSAXE'{ʼn'"=A߄2UdE_)T1B{\ǖ ׭A8jd}ާ: ڵ ~B1}w[oō7H/B]7xȧ,Qk4{0נB J|J/6f *yjF4gnSh%ˬP7/qf1@t|US%="95s{8ۋDOlߒfFDvb,ü2{ %sI/DxR^(Ꝭ,W_}E=όзB2Ha~XK+s e\ֿYhy0IDAT?.$n';E3e΍槜"("( Y/B Apl>O,ʟ"(",NnB!S!2RSf)"("]z}0ȥ0Hg@p3Ӹy@i*s~,݁vN\\E0Yp<>\NɓðkdeJk)FuטoN0Ѧ仝=xK w][GthEotglBgÆ A)oN˂ʸM| !FUuV 5ZuQ78);zi tZ\9(>sAT Ȳ`-o4{uѶZPZMl(sBWgENrZKѿX hQd^ݹعsk=X5-1 ihRE(9bVnNz#$ٵCpS]> Z!M~QM;gvSZSwz>ơJJPdYiMȮ38ur乂:=lYO Ȏȼ14jgLE漬CZL3RQ 8eh:30]*cB50]2_׶svTPҕ=.)8[QoͻԴj-CU|[ƢBꈸ+-6ec$d!Wc ?@{.oJwXS/I$0I$ʻnغW ᵆj?5c_zO91:2 =?C]fY~-䵛A#@SN4<'3N%0[2m/~co^#o8g`M\cW7~o ~G{6S教vp7Wlq,Rocق;Sx3*M[Q&$܂|l;>6ONgX#za7VEizE]umjy3RÜx|$ >5qَk{ 2oA&p]웳~9a_qYK\|q\,E|ƿT_>oRI<*K@͝@/KǯsO쬹 #猦ܡ=xB_);X:Yyh?$qd UbX:#jܛЁ#t[Aw`A9T -ț[ ~yv -ǧ~V&>kxr^ qcLP9i@ ݅bCٵ e76ub gq7漶Vom֣}} xmh9 rqE {1}h4zwSW'wow=~[;`w*Զc96Tl[ύw+CJubF4m W93ETz륍]m9f !8(\$w8)ڳbsĹ7п)ԝtN(F. =#CCqEol#Gv\d$բ^c#:?b8B {xoj|\;::N*/9=tv6E  K eȆ3?4Z)ZoJkȧV|8-mr`ʞ\ŞܹFDN)D b7l݆^y.Iwfͳ^`s\r{}]}DZd{SX1?{ 4ĚR bD ?Fld=؁cPMU p@ݞ;&:){%n[XFܠ둽ǠuDOD7;zw0# {l923usOalnFsM!=#KuȽ9yy&Z>2lm;,|gD>{'Zi6̲j9>Ru~0;7NJ=7?lݴVC#e׎iJ )TKA ݈tU2 t_K߈y^h;}}l\m}!+/lhVG8щ֓ڮ _ca_[ь͛TEA~ڣϷw[6wi,<?㆏T-@`u%\htW[暩rM')|h d񴙈'~Z'w<;Mlr2[\3F:d<4Z[a ДjzmBkg7کoGrZQd=jl{1+Om~|(sJL 6S_B->N- Ba+D3+9 GZǬM=ZmH7ItwwWk[5Ɋ6 Gʌ$ R4֜3u2{al J&qZXVK!LtUyTmijmcT5Sk*ML 21|8zm[ɰ7q~g˗܌W^y/ٹ0:<&G2  ,Oi`oZ`Jlق97@OVRrč~\[:/nLD}jg|xc?%mV7GaINEgNBKc5Ɛs^θy[^g;G5'@zO184_"?7IgK Q^s[ '\Tχ2 t @ME+L;;OaTݻ'NNK$V};rJRV̀SR \%  A8j#eާ:<]vw(z뭸Ū˂$>DpbE}$ӜuhXhAT93ŀ gA_MfМ6,M`Db%l<nߠ_b*2举G7 Gs.`Vg{1 -'_6@uXd~g1&A! /c)qE`P b˱-SX3FP(#'%֑)ۊi YP⒀R2[gOTbzGmlvE`fdK\f*"4-("(@ ((̀U"("By$TE@P0Q܆9n"("(1AT[*!E@PE H=L5$꺇GBpHE@PE@fj+` 0go&\r=\. |0l؋+޿K.W_}%xv1*py<`2a.p\lH)qȆś eYn9o/ڲkyy1<<[miH2VZ\NOB`]q&)QX0łoEEEO0N߂q:v)l$̑#D, W^[n?\(Cr5oeDn-J7̅o#< 'e-; -#yʲ+rS.wNmouF\fbB>ѦIaY(ޗC?F#~ٹNDv*ѐ%┍>ol󑐗E*?'o! X. K<5EˬBMSy R #V~x!PQyg?IO*A\GD>g$"t>:TB9dy.tC%sP7"FhAZI9dFbșlTtA2 -}Bp#3]BQ^l'T>#uei:?qcV!.{j#:SF-U&HsQy4]~#^?eJT'Rx|?IENDB`bitcask-2.1.0/doc/bitcask-intro.pdf0000644000232200023220000117174313655023466017541 0ustar debalancedebalance%PDF-1.4 % 3 0 obj << /Length 1927 /Filter /FlateDecode >> stream xڵXKs6 QQj/lcn{@KF]RJPg:I/DB 7oOĩ("lM]lm,rq[/~nPIw'_/Dg:EHqUť*-ٯ>vѪ~!vM"kKKQD 2-"KߖBH#I)EqQP-w%H\De g&уf?vzhPbUe`[!AtA{gMGwVWDX<*{4ˤiEČw2E$RUӛr,BE2.pHIƟov(*LwU/_i2 DPg]y&Q z{-"c{;z/s2 2cB_dQ5Yk TL?.Ky@sVՂc*$%+k EΑߛYDSC;=ҚJH'k蕴h{F{Z~XFȾ"dOtfMb%ͮ%pΪ1>Q`OU A#gw2Dz@5!d ؚr|Ó cxz_ZZ%Hpc0o#^ҖO"uJ'=r#,\5#W"+Cƒ? ݓ bOGIA|]dkt_IBO&[d} 7$mуۜכPCW7ʒ)mBlw_ >Ǿ7ކkV,{Nӝ<> yXVzcSaB'6pl 8),ZibU@a~}fyω5u׾Njc(ݙ`]ܹ0y'DdU5+C̗');Yݏ' ai RPS@vP[nZY7N'9)Iɡ](.8)>{8k' ;0n U[Yh/DGUQ qMBiVWà<Bҕal1 䡉[ U:eVrmSZ$,M8|Lp KV@I*ӣ$%Q9:_ -.c,EUğ?j0ig DOڿF:N.ikE@Z1 (K=+RQvvI{v !QF'x14OC=Q^V!1XZRBAXo%@:$Dtc6d3m pv,ɥܳv,>EV/a?M(O?28 т@}h b) / 8AF2h,35rfX $IpʔZa SY 2K'&~)PSZ|a>Z2*$&w¿ȪX4Mll5.<ѽX V1ul+1AnУS.Ȓw;9Yж1m|jt$`v 0"jYąkq'ԱJ :9C^SPts&͊P{ui8W?JuN*NBC';H4 y8@OK7:)"cӤ&"a/z-RhmdׂE :6KfǦ&lr8޳uɛna endstream endobj 2 0 obj << /Type /Page /Contents 3 0 R /Resources 1 0 R /MediaBox [0 0 612 792] /Parent 9 0 R >> endobj 1 0 obj << /Font << /F25 4 0 R /F26 5 0 R /F21 6 0 R /F14 7 0 R /F30 8 0 R >> /ProcSet [ /PDF /Text ] >> endobj 16 0 obj << /Length 1085 /Filter /FlateDecode >> stream xuVK6+ yAwsՎ.ZUB.Lbu焄">|>ov\>",9ȓCG% Kcަ<;ST_BV5HigeJ](kj ڪOxiWIN@u0a%T y!U_?\K+V-4*a/Ùd%?yĐ8@~P!a\5 ǩA!~lF/$J1z Dyd3#%?a&9Cˆ(hpv.Xfn&"9˲~v1n.CB3L!]O>li' lD9/8 endstream endobj 15 0 obj << /Type /Page /Contents 16 0 R /Resources 14 0 R /MediaBox [0 0 612 792] /Parent 9 0 R /Group 11 0 R >> endobj 10 0 obj << /Type /XObject /Subtype /Image /Width 341 /Height 271 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 17 0 R /Length 8549 /Filter /FlateDecode >> stream x{eeQV8r8X8XZzPMb늴%9E9,)K@ P< Fqb@R JDȴ ߙI{a?`3~LNu5!B!RXRmvPsv Pvv%%%eee2dȥ^:hРۋzݫW[ p 8 Ұ!]<܄0koӧ/X_|5k^|W_}u׮]?Ϗ?ĉTgΜQUU4T gl> H"J@9('D=r뭷pu5A}P v1B+QFM6^r+rSNUUUuɲ>:ѣG3jnf1g}71eʔŋoذa߾} Qg]^0[}k փRw_n݁|@ p_;#ksWRRYիWc|…15@ ĤE69R( 03g;z(CWA|@!bHA-u]޽{/Y}+D qC\0no~>jԨd2ɈD1D$|x.֡C>c~!Վ9rQElvE:S|Q1e]Dn24 0DϞ=! /{ci&Ƨ!SH;v`| `0vb| 䫉/0>$O1/[)8֮]s~~z!@AAu.]cҴ}D?i饗n۶)h^~s@Zii)n 6 Cܹ҄s NA ?:%Ibp do:7ouO2)hAOZ>+8(СC:,Y"Aw!!w '… ?f̘('N R:/ TSO]uUy%Կ駟^}9 VOڢ?8~ѣ}QMn qԨQWKI&}>x A^jE~ oy?駟VXnr /NP.$o}Ĉ7of-[ .%Կ}o}[cǎݽ{7c%ٳ3f r%Կ~o 6`8#ذa_ʢ`p„ _מxT*Őv}"8#[n '<}?\~ٳgvp"0%%ԿY^xahsݶmV[! ;UĶK>DLg"V;ԟY.\;,[lԩh#G|{zTϟ?g}@ A|=ԟtFo>lgϞ}u;?/_dApwq~G1gDqޏn<*++x?O F]\\cst3fUW]էO,w;/\qƍlٲm۶^{ ܹ[ow^1v18^Y8%<( edI;+Vu@M-Zzj 5ntRr7Y1Bd?|EEOlIZpЪVWZ;-P\R>٥Լ,iLb~GFɿ~%рɨUj-#}ޟyefWEo"IEIG7տ#,iωuobXֶs9O\܋zZf9?Nw2:a*z o{#CpG-r$3oՊh/Ok~A̭]HUM/0SՖ -:Ǻ+>Q:} Uz; MN'FrԿ诏ྫྷ[`BCnF1q1G L".cp7SŴ V\ӵhT-?/ڤ9;"b$`R7`q L ԿCSc жxZnLsN2Kt9ƛ }xT;*O}^{f`5 \honZ M_4('^LCy˓S+33v8 mAZ?L䞮Wk b#Hz?z~ Ŕ9/]fZ2ek'ѿxooA8GcRr*dJݮ/5^l{W5TUM-'XJ`7[B>之w&y孳k俉OS}D(Y=,υINiҨ?'ԟSB?'ԟSB?'Կ…O>7ܰaYx &x#F׿~嗗1()))++2dȕW^9lذqyO ?pxڵo~}_ųg/~dɒ+WnܸqΝ;tбcRԙ3g4M;{lee~ĉGؼy3NAfOC%IK.7o}^\\ ١gAWܹs/_~y[.[pl_~Fa…1?~|P"VPӝpϟLO?hѢvСC9rԩS_VTTDtPkXGh[ZZ:bĈ߽{w2lXG{ٲe~nvs~5׌?Sw,ϟy,[}!旰y Tcz73g;v,e,~׏7nҤI3f̀"9s̚5뮻O~2q[n9cQtcǎ#G 6j?bn۷oڴg]vu0p_~ƍ7ooC>ӳg2ԟy旰y旰y0/a`~_2̓%ll/aѫW/ bBIOП 3cB ԟPO ;ԟP*TEը?ԍgg^Y,fuRTJ4_ /zzelb K oyS?#NXVr- 㢭?mBՋ*aX,ΘRR ADO8 8aoFDk\a͌pYí㵸nw{==: 9K#Ce7/pSNX5}D<(W\T2R&Մ\bhOSz" K_1Z@43̓n'WVD!-OLeZ'Y=H0U6b,  n1ߛK%z7eɔ5"e:Ctu $4O3MX"a6W%A")zWZl|[ 8^VuD98P?+%C".r s;Jl(SjdoɿGk5s K G%]fD o+"MWT<߃Vxz"`O;hu*mB⑔%{2 _kƔ@m. ڛS_թ?57OAN`C㚦*ECf_Qan _ m^cfb 'UMA)^5 &β?"׭\%vsl.h̥/C _-JV!TO:a>jЧ|WHixo׬(VvoD՗~K|^- ~ B?ŭv0Q%,ϭ>>w`E3s  5 5ޜkm '=DsjJAkT2%Q[376;MAɲZbr*2zUMy**E YSO?SO?SO???旰y旰y旰y旰y旰y旰y旰y旰y旰yD7~;{ԟtVɑSO?SƜPO?ojԟPjKV/٦D[VVCZBJ֒aA7WFg%_ԟPi|zKfm1oXy,gLd)~"'ԿM\ yfްZ"b;w}&\K>㵸nw{=BA^^^.d֭%?` =?Z@ޚ>" P*)wzZjWO p14I ϧGp[X|6$/m-vw Ax@7a^Z>1]kaMVO(:oiX4i)9+<]7f&pRTJ"!1?kc,x8ˢ g& \br͵o㺠15?T<(YMlSlZ>]A^]!Ὅ_WX}ؽU_>-9OhH|^p)f.n놉.ayv~nuŔu'+s&/s.N\0=hpo-YI?DXkProNSI 7hJ:jk-,/&R)Z Qܛ̯RԟP&O?ԟO?ԟO?ԟBi==O_A_A_A_A_A_A_A_A_A_R@̓oRݚ^gƄO?ԟOvB?ԿUQB;W-*s7=Wl&*U?VLN#;ls7KoXQ칾?b?-̾ ^>a\TZs2/bJ9.]OV,0+K>Yz[g诽ҫQTNtp|{'y O~=t%ᅅ?[3"P3\L"=2{欏Z1"|Y#!iVSJloƷwS#/=Oz7?ûJ aSS#dOhkƑ{U8"D7cYSXk TT9ճ2{x9;BuqMQBn2ȿyaWfw˅mJc-G_2{]u?_h?.+o6FIt|5fnܾModo*fbFtgpYfQB_هA?<{#20rY]øwS@em%G!O~OȬOI/;/ΞnL `6 RZ KՉ ^O>g?se7=C}PTEUSGuJ~kmg<,ߘ!}׭p򬦠RԟPs؎ OC}QdAޟUὍ]W)`qoX^ĉ 'Կofej@]7؅l{˯gntbJ:7rW Lg~ezDt/ū\191G_$5um1o@n9L\4sc_,/&R)Z Qܛ̯RԟPO?SO?SO?SO y?a~ a~ a~ a~ a~ a~ a~ a~ a~ a~ a~ a~ a~ 6%l/C<_66旰y08/a /iͣ)hAOZii~4 HIK4hжm_F?i}7oS #'X'I@AAIz4gO5?C\r%k׮e e˖!wI[EEEvb ٗ2J$M UرqUMnr>OZ?())9sfee%# A^f͚MFׯߐ!C"#֭@Fd?; 6mZ<gܺdЧ sܹNbD~޼yBzy tf; D1G|GIGoҷo_}{paPv܉8#ڈy0L |Gn_ODEۘ#t\`ȑ#W\1=4C0z(**>|Ey|@+D qC'ݳ>(--u֬Y>ѣpDA,>n=@󁒒AG?Zz/\Ëƽ#}]OIrWH7jԨ|pݺuX/w5gD8;܉\s5'Ow45GqQvid%ҫWcSLYpaEE+SvɫZnӦMC=Q[5Gq]=6Zrt .***\OotMPo?׬Y/صkFx<~رd2yI(ٳgϝ;Ÿ[dY^#q<¹4,X|͸" xO Ńcd=U6.yx۱< ǛRcwO O OZOZOZOzBPyBPyB!PvB!PvB-PsBHv &!B.&R endstream endobj 17 0 obj << /Type /XObject /Subtype /Image /Width 341 /Height 271 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 4251 /Filter /FlateDecode >> stream x{PSw $%DphA((ZRo(RPVXxN;8;eGqN,nw̖noZl5>?a^~s"B͓ijI}vo I2,jf/?JgA5R2ZP(MVkscJz<9yͰ%TkZmaeFf9j3.ާQS߰ntrTnje imo߿ = OOˇw~}/o.J(06L;6ISRSM=k4g=[MF󏏎;v' Yf#jZ^v:5.,_A Nzet|aPM,c0:OI`lALXf%h1ܘO\n2Z&|UnLXvѹ'IʳN[ƪVnLczM'俶XBTgL~}DFS'utX*K_͘Wޛ&fFMTNH5UM )aY 9kĢD&W1qiEjiqJ.qPct7I f]GU"ShH4y6V!QDz^wZUPRdwDfpMZ^J5E4*Uլ-M"5wU mKZ# Y+T8s#8i)B)yZQʄRl.wyĢ]6*J,0T֞ZPvVxF{zE]_n7 UmJHx#IO0ᩎXjOF"P ǃFXhّrCĢnX0k@*P T@*P T@*P T@*P T@*P T@*P T@*PCO@/Ѭ#_/|(&6̣"eJli O&| EɈS b2 ڵ J8T߰l>6ėCIc5afmG$9ĚCR# !d9o vF5%bRb"缞T]$GuaTBR.GҪmюY,|(j`)mb u^wSn8@7q_" rSn6MoaMQsO 鍩uL4O*5T196S9dƐ9AE'Yo1FQɶ𞪑kUBrF19snT="O%QW6j%(ҿsGMfaD#DO͡dpdD}{Ow[9H.z@*>ϯT29g{5J%2DşR^r1@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*P T@*Pj'Q=CGiLEd$4Qba*PgPCj*jB;^OzPUF{z?F(G FPY{jA4YYjgjh(w Cɔ̂Dj LP"Bgqo9g߇8-:TXFޤyOZD`jne/GԸn:ZhQ1ř]U$6m(vZ( r?~m"rĵ5.I ϭLOe"E5A_JO&TV?E)F["Hx L-K PtWՙ\yվ;.|I޽q|8ϕ`UhUs./me}|zwZoTG oPQUhGjnQeͮ-xR@ ޫ-wT:,zB:j$mACSށ[cXC[g[~+b)Zj=9+nQz{À^GTg%5 ͙)޶TGg^C[;N55V]MšZ%ixPLmuw_y~w 'Jog{ˑ}u۪V3FJsVEhE)YU[w=pԙ7|\@7/v9u[VSśژhM9UbٸI,ʵk;u~ɐ}zMw]yԱC u+׮tgƱtL7V1Z&d{<і޾ݻT;xzgRh|eb_oWG[у ;-/.LIZh12ah6HNq{Jk{7=_4;x(VOP <۟ :gJԵjcoCZZ{7oA?k?vΉ{ݹ5xz{~rPޝUeTgBSnN~ƪTMc+#]X»Wg߁#N9{B%{?wOӱ@A(0z O}z?};~_/:{ı#lV-*td!5c9F:PiL]KӳsWx֖WmܲW =~|߅/\z5u._|b=]g[OR΃ |۶l*_[YY,U)iU"vOXEa݅JJ˫6lڻl}]?9Eg?¾|57T*tSPגE ^L2IgU\ 8-1iIJZfN•׬nRݷnמp#MG82ۉ镣MG:}{A5lVU[zeܜ̴%I q&'I#t^5 dWZf5Vy7nR9ߎvڵ{v޵kg;|nٲyc5Ey˲3\NFAY&V\}dVٲ&-Y9xV)--+ZUWUVV)Y]y ߝ,'+r.N ,f^_* ê:5,6{ù8y+-=#3;gYnrg> endobj 12 0 obj << /Type /XObject /Subtype /Image /Width 600 /Height 170 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 18 0 R /Length 28647 /Filter /FlateDecode >> stream x]\?>-vww (|v, [D?{wws>>o,F4P/@I^SE$"R"+A>ϊ ++W \)?/H^,\"UUg~e;NU-ճ$@`Hh;Bux%p0(bpJ+Q8⍑8@(vXJT(hˉAxO+NCuD 8T +W \U"\QBq%]cpW \U"\Qu鴮4mb=zH$>x.qt1M z>.T}pa锊Jj2bp,"#h&1 bM< \r-zdEJ3OQpDn6-Q,pBŢapJkTV^O/#-P *8zҥKׯ_GZUIyUq%e6daSu޹ƀ5007xٱt76h0pȈQ[Cbp #\1J"7N*3$Ǐ'mQq CoW@8 n޼)GGG˙L7ZojA|*+=?4kCW7;5wcX+]p}kV:l~[0NϾ;zr5~]qDQZQ,rău W^!UX^JNNZYY8qBQdT4 rV:YF@޸qC3&&ɓ';w\`8ErcR0Z0gM럃3=sw8պk~ia؇uCYʃ,V5t4ٵīo1Tw'w58ɉTH(twvK+ƍַ߯8;I!a1?}?~9~i{|?{tbJR\z.މ =>CH@͜9sǎw .A $C@]rEOOo…@ ޻wa! |m\+X7N-:vq帢r6]ժmilIznD4}j~ nyx_zћـź?ٴyKnu匨/,S~;vO87cA 5tWgL j7WVo5g妩̚akآI㱓N޾nϙ?L7g0b`~s/ hp3lf###>|GWWɓaaa@?H$1IbkK.U ;ߟEP5OiӦ9sL2eܸq 11dȐ۷o/'yj" )1 uwʔ C6Ų}d +2pkQ3Upj'gwl~IGߦn 5ݰ5@ʡjc 2lj1 +96W3 inmXO%k~2~-37ΚԂr3t=f~ԝCY iZ,IT[[#F0gH IGGg'v߾}?uꔰDY!W4_ɓ'ӻw޺uٳg===C2?q;[5?Iw4mWhG晝.iJg]Ih~Њ\xK42K5nuz<6|'vh۽V;l,j]6\rˏ܋4,s5]L[kPCT@TѳM@ʖc5lٵq}{?ECJH/_k{~QJTrT.FmT'TK m,b6|…׮]ҥK>B";Ԩ6K 5XDf}?Ѳs&߫>8=U$^̓Xzyj1۔UXbihk< ָ֩/@I%iZcg@Һ1;n>7#⍛х}Q>qE}ԪǟJꃿG,k_#3|#,OBa 7CFw־yϟ?322y2R%(0. 6.PT5F4ۇdB:C6=-L7b:XƠ!u1r o jӺ.r"&čyAvm;zޡf52c_q=_֭ wmد;;_OmԞ ᔀ>F;8cV WT\9\TB @?ܻ+{^H!1l^8=SkH2{^L %J{jco}ɪ_ߵ}ˏK|l=\7:udv2i{-0ϣo:3whM]]>>ٳ{DZy2xyE*Re/#i͓W=+)UM?{ #3h 9튘 W*++iЦ1⚒66#~uɊy)99Rp$"8bK;8f`k]u;g{s\)*k=XէOLLLEibx((=zZC8/ _AUA>XКTg1} i [x- ǯTW@Cj.MR S86]#+WUG^U(+0f>f>j e*h p>PōOFk Q1hlvW \1R6\qTxUH*~i[l $jR΅~%~ˌ6&/qv>bZFU_\1bpલ B80fmWZbE'$ ɼ9;N.vpZk¦x3\1bpJ9qEIB|8dT!q }qE+ +W * z-6ʆ+\1bpJIp!- P+ijB7?0wi KCf HA> W \1Rf\\IktA;ce>2M`AO0HʑW2+W h92+֖ WMT/RK WˑP E`p.+u4y2KVԺTn*ޙ+WW&!CVqEd| I8W \1Ҷ(F,}׈GWRyB t egWR=J'# W+.ZNeȿI++ԡ_B;U K=OK# Wt*F*_!8\@B̿"ʗE.tۇ*+ ,?mK5!WÁ/ibd2RU\1BpŅ~KĒ8Z&J2ڨd*ߵΫJ_m~4Ac9{ #VI:}ͥgp+F^)W~}T& ^J_&>E\jD+>EiNp qXiE4&~}c" \G=|p]~vXt Nniuw>o9% |{a jcĦePvlQN\1W2r~2Kd˿B"<*"[eJg'J9f-JI999P8.[kڹu\> һ~zl9KV,bru0Xc F,OvՈZ'֮פܴ}Z]<<] )++Xq+F^q+iSq+.1JwmGUC IKK=Gbޟ,2Ϸo99W]4j:+Xk.K~ۅqKZq]=czZOB$22r&LPWJlذW Xp\1W(OGPI.[f$h\(YpkDAP'zpLZ A<͡t2yc׬l+:̌!N.\f_\!ۜf;^fh_|3' wϓxˠq庝sIf-,3gܸqC7Lq+F^IU2ʼ3J23﬐yg.Ǘ*A*\~~~PYٙAO ݿ+#!^3h{BQ3 WQcv郻c.ܢnq &eسǢt$9SZxw} #y+ 0Ju9 )WocѦ-Ai~ѧ੘2KL`қQC#2jƌX\aX|)vMmq5j,@t 3do1g7f-r`F'ր.%u|Ūz߂1[uaq6;7N%㢕Ws2㊑WmSľeg 3i '_lvډ8 D2̘{o۾m[r>qOM 򑹹۷_e\N\%R_|QNo AEg^b΅\ɔ'r⊑W׌)LDKkpe7&jS V4mf8W=՘5pإ#hs ծWbmdž Fx{ՈC ZZەv~PX*90Z]VW(`⊑WBec1kd~r *y+ZO՚Gz[yg0(ygx5}D[17eeÕly2 *Wy2ʆ+F^1O¢;+vBLx&p;&g@ĕx +U0W͓yg՜wF .!f0/Oq+F^`tjvx;(xTbUFV+OTJdp丒,\1W( $'S|ϋ. !j8 Al|*_*\I\)?y2+F^1WAHyg8nsht_XFl3aL30qQW2ݽQFlӦ.epUJ,DSKeN Y]6pu^\I2X\F".YXXlb#ฒV`Q=֗>>f*feee)!%d LKK?Sp@S'(n%d%e╤Ep%˔Dd%$C_.kPD9@!ADjFd+FpBCCCTBX!8 Ua p@%O)Y6lاO  <=!!<&G,Tξ@)Nz\puuu#(99www///t?@ A-ؓ 0p \~@<;l1Đ\*{```AI# \. yҒyIII`(%`+cP]^0 (0Pdr\@b$%p%-`h-@/ЁR  T՛|ϟ={6#@2nԨQn%T!y3(w8 , Pq!C۷2ҠAA#ɲ`RG e"zJYp%ЃWw!$QR ͛7@A`RRϟ?'O!&Su%^Ce V\;KaAHG0)C^VarŠ; J$EI(Ȩ!tr+ŕ?Xq7qUG^) WU@^sA;;;R 'SzzzeW"-aE+y*/ 2/ JAEp֫Ѿ _An њpB.[pжG$.ʥB8㢕W]Z\J+qR )W3-O%!zPvrS_rЃ+T׃WR *+ +Ave3`yȘJ*+AnK/pUqLq%OJ~xLU^nYvJ渨ɫ@*+qZf=H wx/]KldJ(vR/x!J<E@dA11E. Wy22 GG'fsƢp@ J\WD \DXW\C5?R'3%1:&> \AFG\R NgA2h1_C_Ƃ4KiAg${&3IJ8?$ D |alvZS5_W7ױXFv͡V\I/+l}xXtpE)-LF'^B#r{Qf'pWբU[^ ?O3UiY+Eigl*]'dXO].1Bk_Q(`őWtd!;5U{[6iK0E zfcEptk 3,Va {!@"c/AlS;On F]XPVq/JIA9CȺTL-m"\!GW уR% yM\ĩ^.ݺTydAʏMYVôC1^L;~J Mk8Tf-qDJ \r^#yTtBr)qSүfڌI2I`s)+XSJ/0!dl<=(z!JQx) 5a)!. >Е8vH=юŪADfIAM9p%i ш/"Rȁ_X/6^:_tgf>|d+,|8i]eɟYY_ohܻnc?ɴY>ϗ ٙwjIS~yC<7!3+K+K QV6OdžƀÂwO-XX}vU故NlRcS@lzgV-/^#o!7ᨗzRdGF{F$^VOl .(?:",4<""РИlx\%ń&U!4NR|\`PPrJ 8O--Fe,O~yATxpPhleEDD%px36iDzn-V5=S}=grCV 5yƑ,Vϫ,LEc[jfi+{nvЪM-3 SMUSW}!xޥ0&I f^؏X56oLPFZn߾]Ú{ȃMd\8^yUUz3_P FԺ/\ezoknFT>XفO6x輒Ƙ^@u;fiEYn-qqe<[z"^u~*F HF@P Jʉg簫F4Wo?Fh2LQ uwp20G s7#$eyD-.?e<2G9x(`$Z xݾ.#\O7T&P6 35V$jنSGs] q=}{t2ս@I)׉Թτ_%Fk|qEL=9fE PNǹ#Xo@X zS7bk\z(ӅM73SܶpIqijy+=蜉Jx}oK_x9!f>Y|Tj[q{qr̷('xe*y7ǾmP-6g tB{7k_㕸t{lwp)<|ퟚK6'*Lz)am l1zPSjMML8= TF2[)Pyw'j-8EEl6.FϪ}[.| Tc:paxz$ɩGAa޻񙞢b[έF8^=_& n}?[ t-YK#(F؁, R( qѲA|␉f쬀_y,G;𠴆>LFI҃!ļ⭹^(qC $}Ս-IS?=y٥x-8 ;rp6hD\-}zPzD }DA K\  Tnq<%G.sJrtM^wD ^KMѽ"p][¸'f.;kQL+ {_o\~-[ u//4_J:^ޙ\^ɕ Sx)pr?ga8@!3o 4X|0ڪC'_q񝻦F!Ήx%&+qkbxCx|]ϓY^U~28{}$f43ӎA?LݾrNN$'?BY&R׮s&߾xȢBLQ*d_?(g2V_|KZ\*+ 1\.my)A6oAOPHEKAnoWjkغ=,y9pVS\-j:{ &q_j*uk@ɷ.:X 1zP_ l^W4j͇!q77y̚b)Kpn }Z [+t ?vMithfO6mð,j:ĝBoV'' _'۴S'fK2_~pXAm Iҏ:$:_'-c[|CH)SEIj5tгC9N Q1Rn\!Ճ2.̘mA=uƮI(”h-#Ť߯!wvoBh83#2"02ڜ"} sBoOt̾)J3K$|YM0o$g]Aq\Q|}i֙&R?:d35w`zP;]S<c0EGzyzxpw  Mσ_ ֏nRF 9Zx /pа- /Zeğe!8<="""bx/~i,+84~r/*m=3W|cX >6ޙ?d__2ݟA1XލCLn!ă8W1lTv1 4OhHDddTb&) Werߣ/#P&OuXtZ5-,-Og[;1Yi~"yoo,]PVp_"1 n7dǒڌ-vB ,ZD: 6߇GϽ,I W&so_D\;2ѝIi6ܽzdc|1{ۍ ? OZ  cx 1?!~?[Djێ C'~+~9y|@н3ԇ:=z=Mxi^}g\ڮ+e|l߈H_"|Q8?mOOBD<ގ C[ )k_ٺ]Ұ7𾐗>L. t{Yx]D`#Bo_xDeNE#m੤{kd܇~X''q0x#^_~esXQ2G'Cu%1p=h0W:o~ZmS"mw19cgE毞C#V·0uoZ"~.i4I]Mf3dO$T'sveO"BD~2D " ^Zj6$ dF]d?h!1X(0Žx5.)riMFnu\t֊& \+i ;9'35z'PJt0՚Y!Y|wۈU\\4~M>yD.<=Oxž7XᲲFn M{ڍ"핒VdLjA,_l$ qQ"V"mڊJ׃Y=< Vw7NhYq\i筭؍ ;%נ n'(D x.Ak[g& Ρ+2q\qg-^dgxw6 ͚X7eq8W47zkḫx\t#_A\t.>1Ss܊ HN=^hM՞9 9}I GVFNw}&QVL}W($*.XbPf6iDnnign=q]kI@}g&[Vw^q;Zߡك,쥁OX{mPڞ.Z57x "[jz ͵?habP!/:X{ugk-OM_b11\lkIDm.y-PWlキ29j<^ri=_+פ1T^4>Z0辨[%EWc ӾC{p\7;gVmUr \ tYa&.jTlo`R.y2+p=kXx#O?ku/SA1H0}ɷ2rDIdGGvqܖjih/qۮϙqA6Nvm"mk]imL/"E1^=tZ7& 7xϟ~EK"fNIf쩕gol鱹\־*<鑎vvM-HpwѥFWn޳'W)N?pCn8ݹmr!Q">WJ>9‚B# B~I%q CCUN)sr;yd3||}2y 8 K&Ytڥ ]1 J% ;-? 80(?88{|ov&)}-$N`!S׍_|ūVo&fM"H+!9pߑ2O_\RM IIɉɭ;>| N!6c }:[J&o%măn+dcs yQ/__[ݼ} GҮ_<F+!b|>~[TħO%Ɍ{[H)pZN_1F8t Ġ,#yUz.$ z)&QKR+уJOQ/D/TeL$*bɷODbZ6j~Pyp%E㪌UgH8yhWUc_59*kL9F;[ QFN'bЏ1A*O koiB .B_4K{!KBC›Q|m QhqEWR(BqE)PƑyĞ͛^yU+ "D\*/h+Z\I%0$K O#lJeCs6'mSbK)=q@3>§qE=?Xp2u+q|p%]TZ%iK̔WzPqUQ WJbeRg,kFH友IIgMRo^qQ<$i㢕Wǯ WbR\JqQWU@^)DK%B9K5$ ô*/3 }OYV,1,i`2JJJQxb!FA@dh *8-yddƕzP9q-R?XpeΓQyX\Uy=EބB)nrWM`;U^)$&mrb_ʇ+QFFJE UJ^) WU@^qXGz6KkP,[;xIPTz 9gL"KHb,$PwypE'SYp WqQe<W=:JQJQPLVr*7Jbt l! f踒V:\^#?XIq%!]G_qUlz6_Tf_IWA٦".<8d~2Ϯ(PVF\!TW(m*+ +L,_ޞԃyyy"̀” SN2G 0aʕo߾eArǏK.@n[L3wc %  K!p6dq 7@%~H y YB8D*&h"!6m$`&]›1qDf,^kOMGHOOd" A 8[k  ndgg22̎;CNNN@ͥ )wBp*,,Ç 41bp&NlٲgϞFЫW/555jðaƌӨQCVfQm۶ڵqFD#'pA 3f8wܙ3gL2dȐA-Yʕ+0Jжmۮ^nݺǏ?vإKΝ;  .jjjJ@-we׮]Xd"ˠI PWW۷pgO~M=r΀nYp Ͼ}@ʕ+Yp ŋoݺ= ۷o?\Vp `88 *p{-]t˗/_~]KK <ܢSN(*پ};8&ĉ;vB$A㭭#""'QS z7o^zRVX:Pmݺk֬Ye˖0Z 0(Aٹs @3 ۰DY Xs/t 'QlYGhƍʅIz@AK Ց#GN0d3cq dӧO$^"]&@=yBepzyw7o^zʟ3ϟFsww 9 ^\\,lv~~~fffRRRDDP...:ǏU\Tg*dmD mP$X9ׯ_)0,U'/( NQ@999&șOOp /-H OKK#A)ʠ@N___///P p']Uu"៮GdǝŨBA̺QFm#BX`zzzz{{ We7 R+444O'2$&&&O0''x|gP|]?`\&І@{DGGG$E $K,+"ĨxOc ,xT *999%%MU.^dHV T*t_'2R%|nVK\i[ OʀA* 8kNf)4wUwTլYbS] CJQ}S&Jb V O/uR"-EO5\@Arʔ)eWTH$ݻEСCI=p.IP6WM,J:RTW0!?!WXH(P%GIA~aҵM77G6iɥփʆJɓ'*;{5ԃ+ e!5d6DS7r}[ծ]qx=ZiٳCrJn^{)[ Ytεv qa$^jX$܋w+O.]A~#wzX纬/|Zט5# ~%Xl5փbm>!:FX]&X4:5lu&7>(W4=Fv[|M hӽ﵇ v>5ej+oƬ¬ujӢuYjޱafocr-bj韻K×^"+A% i^&J1a988ܹSJ\KľDogb:m?%;vz64vjE+k_t*‚,i&*qsZ>/#+{;kG5+=뛧N_CUq}l7o@cp2Cf??}S]kX^-xEԇq ^R{6luhNwlz57hrfo~KOxrnI~ ?8?>*?㻨ga 'kut`#OC@Ec[뵯R~nخ=Sq\?X% ygׯׯ_J\QP$BW)1~A|Tp妽7fX [gRSWR5 ;w͖sYe%r -,1@#KXGcX}ƃ8m;:7gf禵ohh^Xduĸ^ZuYFׁF8ea!ݨk\\Q"MV! ^q^:EDAfWU<\2 EDCW֖$!ga,zS53~w~}zm'D1?hjWsTϦ}#SڝÒ޵dV\}dAQ,CW_gn{tiFa^bv[:w6=忭OUoYs|6>]nuuj3;ӾMFne^UAjfr~,c,΢e3Ѭeim<ŝ5;}Ci޷:7bM_2t`IaujN;כ=]7/Ohxvݢ2l^,x]J=(Z oU3.\3j8 +qw<32,@d9~|m/ ONt' 3:,3)qiy|qRldv7#!25 Y x535OvܴĤdܯI|M!KYoΙy^I Q jÃ]}b2✨d Ȉ|P# ."2pi~X=v&],17_3Rc~;ۋ7# bC<'&$ek"b"Ҳ)qZr_UWADόȟ'[ 2R(qQWeepE(UK@)k%89~8RPZ5IBFьX;3z[UW #J+tlo;)]$)Jx7NL .r!4\DC5{"\!e<>W@X-nU WzfUy !UZ^# +-ڬ-y U=~^#N9(px;osJUWAN)dGbUfGV%\a- >E27R ͈O> A'3-9#"<.JJJ3)ii92]2ZG/ rÕ*+ZAWRJbQʽO̯>Oz`¢3Yb1_AÆ#6fRXVcsmb._oU*߫x%ϒ~6#Y<)2ɑ<iD _3o㽋7T1\!fP$dL*p`3m{Jke^EpEp ' ʽ-+q]ǾDzx礇FD6^ q1o^6}Sؽ~$8}>1 m:ff3HXکX-:2;'6>4?9~K{ZEt+jpwx޸򐩃-n໑.]}?XC-{4om =s9s~J9'}G_[E=]flVw"C 8Nk]TRB3EmnԲe<vᔛU˺+8"gʁwҹsly܌ߊCGnۍ9&YX0, T@|Yc׼5вX7S8a^T,vְ߭Yԧ8ukE[uފ9ZYt.)uz3'H+ c-?!}p{^̷|H8ovA7%: C6jYzԃt?R8iN8Z\ڣT{z'(i ̿Lָ|P^AWXu _ezyՐ匩qT-նz:+Bo3SGg^;z>}6=O>>k6Iʤ {3A4Gaxw k9d$dOa5[i@ſj:)}.:xO#cpY zyXHN'@ \0ޞl?a%ԃg|pyF׎p䁿/ÀY~b=mcMՁJ"o٤'=4vO 8{ D,x0qة 6v^t^ަ13PrNE ;E6hX\AW: ]?2rڼjxYe;`Knrݫhqf1:0hZ\AC3#b!cVul:EYV:eԣP}͉U!dN{L߰Ac4\=]vfGgc $Je$Y~_ WhU\,}%uBhILֻ72},IKYEk#QL\5vpv Vɣfmսi̴pN<dk^ld7 8DE93e&2XI=LѳxR949u< JaWy^8g0gOϽC1#]ezLApn/'O^wГ"uYQL&WB>n-4!%%ă I0S[Qiz`MWW<'‹d1LzɬqT,zJwӓwwa/J3UG*L5~m|oիfvX-TMYka}| @N&L+$DWu H]\ QvpI$_ Do'OCS9@+/Oߵ\eUr$}=˗,1'Ly36_\vh箽9AvxWH^$v= $ -=T45>$MŊ#k8;NPz,=P$ppEW@fb%!l +eBWWBݥֶD*e\)HAkm2W+QX&\qwD,Nyg'-ZR OJ8"\!\!\!\}_h Bk!g8O q;g endstream endobj 18 0 obj << /Type /XObject /Subtype /Image /Width 600 /Height 170 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 1182 /Filter /FlateDecode >> stream x݁n:C!G2۩%v@33f}ILэuq۟7?!cܦ}}>?^yInXbADo%OC͑wՑg:RDz%:̯a;סC:t0o7oסuvC:tanzQ_a;ae<:&w?t\1o/a;ص\qCA/ 0C}tX{>>:tXߊ1F:n_}S=tn9Y(y?ݼ>d@֯Kt_uСCyw3l:wKpZF0_үQD}.! kuu8> stream x߯U?!*(R/0jIiA*㪅4-ⷥ 1XH D,!_P0HXL;A.Ⓔ869$g>?k9{E4{o?^Ygg9<"yW_yۭ]vX=Pg†4ywt:fȀU* 2oܸ/UUT_~`ex;]OpjfN=00n޼y uK/>B=_" t:rwUMW~eЇlӑ{Gw6J@6-w_Yg8;¤ƍ/3< ,f{l `ٺ~ ҂lZg}ڵk8ڶ 'e8>xǞ9?=t-oy__H r ܧz?A][Ȳ,O}Sd{Hw} ,[]H i>{_B\r_X,;',{; d_ܶnF̲k׮]|ҥK-ڳe2 .|+_wYmyB쩧x" X,̓eׯ_߶ իe_җe2` X,ˀe 2` X,ˀe2` XvXVyˎ,+*,K,İ&;eu$b1lgrR\)eKK`V(^eIje,qemˎ22o6+*˪Рz5ymeEYM^f Vl(>9wXoYF}le,aʲyٔFͱ:m!t|e rG|(.˶euj׊bx$ӱTIFCv kKNTN[W_fZ(^Ajb$lF1NmjG~Ҝ$ve3`!uT-ۓV8f,cYl8L]IMRx^hD畸3J5|>AMu-C гhض5Vʸ?/~:fE^eYl?e<^݂|Y\eoz,;DMmF7V,fYC<2ju7Xv~X< m,b+nvX!>b}!JoD{]0[2C Y92e}5Uj UIlL-vɩ}"󪮉),d4Ʀ 6O22|n!ßfꂿOpš$s|F4mVB;t,h MYVޒ辚^cYd1%\{lr2O-;3N*E)w>B2^9[434SPC'ːeSYvE[0νs²׸~kbu(V)MK)£"#5Ff~K4#:016{UD3IDU D`XKDe}P #'&IqO|X^R6M'Y0.Py,,U6=YXw^`dE[@:nO1#b ׺y~k(A:At𢓅[,bY "ƫa3HIdC~k1ح@M`&Y߷XMz;"͏igQx=h6UŽ,›,߉ŲAV&P[͗a˚$&$-ߎ_Ml`t1('{Iwiiv3fl<4en v.D%ĊGl  WX^vN)WưlJP5핧YϲN922q\U(hLѻ)Qե,: F Mb;t,(a Rk>Ղz枕Nm@%>byb `ٺ$ipHiܭb;N왲Mxmn(16+"N1gApN6%v$X_&І2NʚsjEIz퇣y ^|utm?g允Ú%k{$cst)v*EU/ԦJR>N8#TƟvo(rVf.E(E}仅}D?PdI`I'/Ue24k/Eq4(MY-RtY@#@_z~e]SY^" #B,:w ˣ*G?K_Jޏ9;gw+4 X=+D<'?qG66Z=u$ 3`` X,hC,;-9sUa[ -e 2`٩/(JVx$X,;{66 X,*6/2` X,ˀe2` X,ˀe2`IXv5` X,]ݾ}/^ˀe2,{Ǯ\q-n 嫯 ,ˀe k=.]z'$ X,lw}qyS3 1AX,l|g9882| X,l|}G/_S[nePcˀe G|`*r$ˠX,l˾/>䓘e7oˀe2` X,ˀe2` XQUEKueE\I,İ&;veu$b1|. X6esg,Yx=B& і]} i,;sm˶euUeUJhP=6˲"1ˬ&"RF6D]2n0G;zzeGuY7f؁)[c~!tmSp]cY}D9t,U$PCv .a DMJ6ewGBET#a(c&Վ9If>uT-ۓV8fJ,cYl8L]IMRx^hD畸3J5|>AMu-C icDV[+ h YSxٮ/u _sxyQ;g"e7q0e'*6 Ȳ\YlKg[qÊ-hؖ*ѿul P)Ȳgu1˶5樢}5Uj UIlL-vɩb;snU^SQ& )uж d' tHA𧙺q"I\\:骃"U<]@#XnnͲB: "]՘6mbM&Z$trzP3l`6u1d6L<&IIEXhC%~Xx0ƈhѢ A3:.616UD3=CAT̲gY kI躎\:dLˌxźI8ɂe>yAB^mG> .Wk5IV9 ,C3$#_9b+`:F&l2ub"]luAve4U_p0Ι? nͤ)Y_rM1Fɒv$D8 |f2F`WUʲibBGNq.gtgK["1$GB8KcVev$&$<'IjzYŠ{I{Ѯޭs1Cw,[oqn}uX~M t]]d+|nĞbze &z?lX']ϲN=IeaZe!|.r@lɋh!50? LڄZ~,Wd,wIڒי:)1pW`zeOCiN 2Kf( +uues98)興j'h^@o *`Yff\L/W7Kl ,Kls]3u0Q”>%Dkjϲ7:*:]rm˧u;YVhcDWU(hLѻ)Qե, F V5QFm. dz[ 5BmӀbw:_鰢^'yV:.+e-^ceˊ8uЉ#h ,kW>b<(3rֺԎdw]˛Qemn0'V$g~82s~A6*7=[~D 2b 5KL,@aKm4UQT,zYx6T/o";tZ5 MD2ZMzL 4;Bh`1Jѕ ,;9]^f8~;j ,#yí ֋%Y0xHַ+I偊P؊ 7rCU4d/*lsp(<gÊd F[w.@G0 X7(-fvBULphf~Yg I_ux* ,[p\㰏|PluAveX=\vI?\a仅}D?PdI`I'/Ue24k/Eq4(MY~RtY@#@_z~e]SY^" #B,Zc ],}} X͂jܰ,ꄮ ,;cdNyh9 Ǯ$mU@@eDzv0ӒSp*lVrk6 XC,4 m-,= ,˶13` X,ˀe2` X,ˀe 2` Xn߾}ڵ˗/_xX,@@en: /ˀe [2^=SbA.];~[w_?#?70^ם:6Щ}{7 {cN~'~ @7'>O~xqeA@^y^eoy[`{Vp… ,c<>r|?GG77g}*FWe2` X???u//4uG}[?K/aHgSO=,@e + |3OO__<}? o~wnܸꫯˀe2` eY WF$W\y^ZȤ X,@N.s8>OícnͲBXM`ّ;8>(,v:u']lFZ.lM2eHoS,3i٧hvӡtv^].L%r_]^sh(w sͨ~3 Yxֵ>ƈhѢQq[4bhlmPU6/;`ΞG$e}ٔYF9,2c9h.,>*N` vټ2!B; X&cYH궹Xz쐡MIL5F_255Zx+t"X61`YKAIlƇdwm, !ʢ spR7qYQY9cpb'I-V~"͏ig3sWU$_m凹1SExs=rlX6t9;\]٤>ZKxQ[nxGex"Pr *#U8X: Mf5wuY6_( )ZX&=V/inksu)#u [V5QFmezRkڦtF AxlE\f6w΃ l2w;SA);hoNBQ,D Xw3V 'e D[xkub {vP4up>T'mrlCC6G NSXӡ%IYN a|o/ʊg|ۏ趻YFcyaద}Z䷽U_ѥHRTEQ(eڔSzбZ;^S#n2h5v'p2tE&SmL᙮rU~ /i5|Y5/𴶖Jίza73e[2 7P̠n'ADžd}]caEjiQ*5pٺiG3(e5qja$8c+mTٺԑ4Hoylz2`à|._x١j }T3Q2fiF^[9wp} ] ,ۜdJ2N6[lhaټ*ΖY"YY[6Utw˶e,Zp,B~,/5׊G[7HtE trPUyY{,ciF@i棋|<]6H;З^_hYiሐ/j5vG/hC={?ޚΠ3auMQԻxdkߓ+{9 |'ڳׄ垼ݏ;"NKen__- X,WEE ˀeyA+2`l_Y@e2` X,ˀe2` X,ˀe 2` X,-]|ŋ2` X|C7̲(n޼yp탃̲GyX,@V_xgβwwu w`-^. oسNG^']~~C;wK73B0$I{ҥKO> /p֭3aٵk4}G!/ ) Cy(???3MWgyիg²W^y駟m}{6 Xr ˿~%}K^z%"I^1Ye2` |39/|#/^׾o}{Όnܸꫯ ~Y-1ˀe 8?7'SO=Sg} 3 Yy X,_ϲ s9RիWzo޼y&ŎeҥK2` XX/})I(^z饛֭[J:ge2`Q=#8 wXˀe 2,ic\2` X,ˀe2` X,ˀe 2`FYVyˎ,+<Ē8N mbYk輲Dj& 8;.'5p Xs, {fUp Y8nі]8,3e2r62*˪Рz5yme%4We8C19ʈ&]2n0G;zzeGuY7f؁l|^6es,-/oWcQǥ7n;.lYV,p(Gr:QxN1K%NcD;jw ia  D{pJl5>% H&*FœvnT;$+/Q9^SlOZIe93u9$5IyQWj@(5/.E -C2icDV|, hRיl+D FC[⧽=d)BSfTN}c4L}ىiձa9fԓY(^=ƈhرj$n A3:.O7iQ4"hvʜa@a-]1uAxrl>aDGu%e3RqbB a2iB^ms#{+u,,Lp0)hPHH(RtPc/ H}=fRY,8nuXbŪX\ZԽ6&>Y|X+6Xރ¦_ѥHڹk(*EV^<uPf5Ό}Se]U},{;bhXO֥f#m8%._x١j }T3QÝ Y]Mˤ.-&4,]mbim=I:ǁ=}Vii8̓An,~ی(Bu脢{h;`pnr,B~,/5'#-$"KK:y9*yY{,ciG@i䘯i| -"o7eQV[k\1C\U9s:`Yl礁i(W푭}O/Q<O48v-+ֿ'aE@e`YD;B`iֳԧZe 2`ֳ.(JVx$X,Cixg` Xvj,S@e2` X,ˀe2` X,ˀe 2` X,]ݾ}_*/ˀe2`c٥K~iƲ`̲` X,{ Mk׮ >S8_uQ/\YV~~{w\wqF̵N~g~fozʕ]aH|8 2 /ayfFCUU;Tc|e˶\=X,NЇ>/a˗/?#2`e2eW\~v12`9]`,2` X,cˀe2&Y]ɇ~ˀe2);e2` dYcˀe2` X,@e2` X,UE岣<ˊ;y"&$jXVG|(:,,ک,7ƎIq *k,Yx=B&be,q1L`nܸ e{ʲg%4|rqeY=c Uc,PL2Iy?юefQ]Ͳv,MjKm˨cQǥ7n;.lYV,p(Gr:QxN1K%NcD;jw ia  D{pJl5>% H&*FœvnT;$+/Q9^SlOZIe93u9$5IyQWj@(5/.E -C2icDV|, hRיl+D FC[⧽=d)BSfTN}c4L}ىiձa9fԓY(^=ƈhرj$n A3:.O7iQ4"hvʜa@a-]1uAxrl>aDGu%e3RqbB a2iB^ms#{+u,,Lp0)hPHH(RtPc/ H}=fRY,8nuXbŪX\ZԽ6&>Y|X+6Xރ¦_ѥHڹk(*EV^<uPf5Ό}Se]U},{;bhXO֥f#m8%._x١j }T3QÝ Y]Mˤ.-&4,]mbim=I:ǁ=}Vii8̓An,~ی(Bu脢{h;`pnr,B~,/5'#-$"KK:y9*yY{,ciG@i䘯i| -"o7eQV[k\1C\U9s:`Yl礁i(W푭}O/Q<O48v-+ֿ'aE@e`YD;B`iֳԧZe 2`ֳ.(JVx$X,Cixg` Xvj,S@e2` X,ˀe2` X,ˀe 2` X,]ݾ}ڵk/^,ˀe e8i~7nܦ*eA2` Xg-/2^y啭0|/]#PBxK=to|ַ$X;|ԧ>~L,9~e큼Tdr} 'A[2_2` X,'{w][nm[j2ˀe {a u]opj2` X@>|x^~-de2`byy/n[7c2` d\p+_w̲-/c,# X,Y }{ˮ_myˀe2` X,ˀe2` X,ˀe2 s˲ȳ\vTgYQ-dYbI'65P,#݌e;+}L,U]X*Gz,MV-c,hXv,yE l_YVWUYVՃQ];n,-j*".D]2n0G;zzeGuY7f؁l|^6es,Ne_%`,ʲmaY}D9t,xPha%,G֕o*W(;,|*"} utۤڑ4']{ tHe1$)X3+N:SCRT8EAy% R OPr]9A;,#6mM`չ2YQFYďxٮ/u _sz:FFN,)cSQ973z0e'*YV'kLco :ςf[:؊Vd'eb_m^̖ 퐂,~VgLcbٶbh_|U{ai_S@rfkbj & )usö d' tHA𧙺xq"IQiiB.F("U<>ACuh$XYL %k5Wm0ۥLl5EΌJtűW)eM{uݬ QI|4>:,bQ,[xCee]x{e1,RcocMx{C|xֳnjLL.,ܬ t#b &/Ʌ/|Lڄ,+_IMji ӢәL{ame,$^g<0!e o(ܰJϏ24Y^6xNLC Sˆ,cuUġoj͖M?.CEI^M~1Ie?E2Ŏ5~ G 2^-,CDzȜs5k4/r*tK7鵉#v4N*+:reRNT^ [ƮX&d f C0{Yݰiv^/A1a %{THcdxGG SFKUƳF獎'N`gciy%ZXFO'/inksu)#Nb@eBbߵ2JX-Åv ޡ+e+굹gS[2"hIOXb)rX.I&@w؎ӹ1{,F,c)^[9j ,;}͊SL7Y4eysIC6VG L;ŜZQE`.Ȧ_eg|ۏ趻YFcyaద}Z=(l.]𼦩bQd)zyS/CDzjMƾc437Bn2h56ٓ_MIvqfGQ<,z3#zVeǓo3l2Q(Tg`%N V-_vCe2x75+fP5Hic" 0ǼÊ>Q*[1UV:xJ?mJ>FIq=V$/7"hu<@e }2`]0CBg7xEt^/>H:0,6?T<}:;"#d&ݾ ,;,ceeyQ9snaD Y'XAUٿ:̳4 FeQ%M#="JSV%|<]6H;З^_hYiሐ/j5NJő6cΙʢf!Aǻ²3}O/Q<O48rܑO~]$Il,X,""NKΜeU${C{` Xv4 (,=,vEeʲ X,ˀe 2` X,ˀe2` Xv]v X,@@veo,|ŋe2` d{+W`jܺu [nAꫯ2ˀe2`y{sҥ'x"W^ۀ7nx:˺___7ЛK~Gt:yk^?+d(l^|ׯcaBLpZW%A^yٮ86V L͛Y2/ˀe '~u]4ˀe2`V}q85Ð'l'ˀe2ma>3Yƞ/ˀe2m~>˗qu֭c z?e2`6G>1ʲ2x X,6}_|'1n޼ ,ˀe 2` X,ˀe2`l,<˗<ˊ FGXljaM,#|Mw>0Hf5c`s]`l*8X*Gz,Mt-c,VXv,6m *˪Рz5ymeEbYM^YE|lVϻd7av,>6ˎnͰ S7k,C ,69⻧ ;Dz:5ekE1<ԉsXHҡB[$-u]Չ# AJl5>% H&*FPRX;IMIsؕۿ̀}/[)'Qp̔XƲ\qҙЈ( +q5g j|" ZءDƶvWx6XE,]M_/nA,LL#wFπegEn`NTluAveγٖ"f)G[ Ѱ-U#Rȇc*mkQEjՀݫK?ؘZS6vܪ)M^cSBm'NVg>7萂O3u'8,PEڹn{*e2ų*2֒(ucYgt:2aDGu%ePqU} u!B; 4/ .hs|]j,(rvYRg±I:FpsB%6W u̍L`e"Oi&qE٠$"-ia3H8IS:]eD*cz?L%I-V'4?qVelǍjD*e`fy;, 2]8WDb6IRq0" 4iIL$IyN8ZeA9amQ][߁5c:JX2\=D%7 2V .׉=ŲM~T1귱&vO*OEevS{.,ö>:)7kC<]=g1|刁*Cj:a~@ 8 YX%iѩ3eu|Sb(=2uAve՗Px~SkڗoXܓj~1G"PiXYz@mé^ƃEvBMk`1ěf l!7Ge^'/$=@"ivђb7<+Xv rupw+V]d7YF/[ ԭ;J47a𜇑oq3V)f2>۽oQa娃4 h'2_UPఏQy1zx)l]&:1<`6=PoP[`r/Й(^,Wk.:|D-.' %4ˉU<X~ẇTaؠ(˘3zr6~,/5w #Ȓ8N^ei^0*X/-i1P(9櫥鲶Fځ2B˺ħHlE G|YTn@/8YBlU9;ԸaY ]dYvƲ_%=ɜ.1;r]I۪03e`e%Uؖ${l`lXVi%+<,ZVY{IX,!m?beg"2` X,ˀe2` X,@@e2` dݾ}ڵke/^ˀe2-aٷ^xՁ,f˗e2` d{<뿾^z_|饗^~庮oݺuj˃4M1~~S_/XuFOAS׽n=_ƕ/''^LX,wqooJ}?T>Ou<zWee<ee^eױӁԧ> /L lu5F` X,r ڳ>{7n S3̲K.ˀe2`6?~_s e2`˽կ~vUU!X^ X,lc=O,֭[SyY4 X,r/>SW^b/ˀe ;2yW Yˀe ;2z+J` X,ˀe2` X,@@e'gYUY^.;γ,$jXVG|([Ցn,9{8m*h,Yx=B& і]w FU,;=`ʲg%4|rqeYmdVWVGc".k7 =hͲe3@Y6>/ȿ9^2lN{z&svYF4mlۚjseܟM?aU(b/”x_O(}3`H[Jz0^@&,yhK@ZA=Ͳ!3Qn?%@veγٖ"fI#a[*^̖ 퐂,~VgLcbٶbh_|U{ai_S@rfykbj & )umIYF :yL] 8c :c ߮:-rZC б^^cnͲBD")t檍fM|&hޙX Irf uzen3 65^=,XfNl~Kr_*^zM̡Au+ٔ.$gQXW'È#5FVM4#5%5fhg(̍!UEIhJXMe.>eR\f,5-֥M2I .W'D^hˤy, )zUr|]&X&YQ(΄ۓt|戭X ׺ ,;p͖RJtMljc"ƫa 3fd`85M,X&+( [&Gi~L(=%̍v|WX[4to, {4glrFwƹ=I|x *[͗a˚$&$<'Ijzemku/iUr.-5n̘uNVf45`^/TWE7\GW1,cy7 ]dX _a9{AۅT'X^ò 'RcocMT>g=:vU&YaIY&YB9+yk-G (L^D E_d,#8 Y&Yoia%)f 㔘QXw[,dbxR/rT*#U8X҄lK8/$(CSet4t8żl脬/75mؤ2ԛZ^EOߖ{z^QҹN4Ser'P=ZYle9nkcyS!9^K/鵉#v4N*+reRNT^ [ƮX=¦Z?jmN'Lx[ ϲ&"kf4Lw{0eAO{ њnx$~HVPgciy%ZX&= 헉4z75j:Mb@-ME(#϶CDzVop!Fm%NgVksJeEтRpkڗXܓ\ ~1G"`iXYz@mé^ԋбZ;F38x-Q(Vk=޴m4;B`֛Qѳ ,;u)ۇd 7ͩYAwVϗMqgݞ֖ii}Kl;ްRtiC(ŐDžd}1ﰢ@5lTDm7|Z >O}ӟ?1" WQMmmm9sq0/ü`g?Y4JS>~_Wկ~|hy?#~9rOa?>sgb^,C!PP|'_׿>vʉ'~?s/??]v֭[M2ːe+.XcD!P~_5{W! z-e)]4b"7U7/j>!(^i@Nҁ#φycM/dYֳ!Nȵ;Y,; whĖNV4L` fXVU|-QmS%0cbd"р㥎~1Zu"rGz "ѐkb= C_F0;c4+6T82DcꐢJP%^bBe%kez_2\.;ʦlz-+/ћQHvX`7dFGH5eIl/rfXFMv8reҐ= ƞAMyv6?ɻ2nVM-!ʨj+Y K!hF< lN#?z5Ƃ=.ZmaB01 e|)UNX&8 =QX ޯ"Q)"0eF4KOB:"%3:&y,G%蕻U.[ZD# -6a W7&:$>0$Ai"O& 5ۈkAgR^q KEFPt3e;ඓ܍7܈,+yk&~L=yFRJsri~hr tA۷;vQn~ }ϱ-&tqhu_ +:5BU^f052ݖZ_q.UuOcI]o`9 O,ci_J8Z*iUwYtt/t 歗9e}5&EZ:n5ibESбn lXF _{,Lòe,T @e>άNDcYpgМT[eߍ>JBsZ:EC4(tFĉM~S @I% ^\fFzefS^]|j3/KlտϰTvlSJ5"X[5K%tQ%*ƄYY,+w#[Ͳ9yH̝lƣj0a<;P]$Էé6DpVr*Eg _}euVŶи9CaUN{ mS%u{Ckڮm 0K|^>t!jyT6Qd͕ LΦע2{t,]o}m<$pq`CbjXXFȦp2z2sӹ#dx{r O7vȲؤtR*Eq9IyD ? o[8 è"M=m/J!-O%{|(]Дv /2Oaշo%$Mzph2ޡtQd1u`p$i"H6WȲՑ}O/Q<Flp +u;վ;d Y,Y(Yy- 2dlY'3<,PX&K{w Y,[ 6ed Y,C!Pe2d Y,CA!ːe2d Y,C!ːe((˲7n ːe2dee@Å ^zcjyJ̲K.|O<,C!ːe(( w}?w\$|o}ʕk׮Tn8mmm|!G>я~^"ʽ? k.T;cYXC􄷾 dMR>/| _xNȗ% 3' gy20/ü 6wyH(2ːe2d 6+իWo޼0]xY,C!PP"gN>kmoo߸qc1eXcD!ːe((/(~W,[$p# Y,C#K>O>s (ld Y29|SΜ9pY0Yid Y2 p wX!ːe(2lB!ːe(2d Y,C ːe2d Y,eYGq:<$+nj nJ,=;,tl lb-ȲdYre.udAc١Vf,DݺFdZ,ϲ4a M7y:eY>d ]?4__zvQ 5_FhG2{fn]Ͳ!v ,>/Ք'V-/#+|eCf[n~X{wkdJ,6ߪDR2kI˥[+\COEōO`Y\e#lMjkagYIEu P}NѮJ1ۮ9ԷCSnqmԄ"NXSKf8T&)8g"1fW`ʦ$2I2iAFr49Zl9h,+sq<;7CXC;"֗e-W2+Dz4/ެ:t,0/kn̳wmMaFw۩aV=xr1sƦyYo#4Ltk?H؂ư, c]of ދ") )yڋӳ,[Ԛ'>-(U'J󽱌r-zy<Ќ.ge24{({ t Yv,7-I:6 Z-`n^t%ѱ]Վy xҜ,Yt89u]YEZ)⥮ZF#vYVG\Ǥ==:0d.IP^NQdIAlE%[Nb65rCڦ["K u1P̦RёHٓ-z63+ '62 RZ L5m6%c;YY*Uӱ4Y`gxZbxK>jLבeƲr7,KWxo<̡ӼÊ< EJ}K1jI4g;)7Rt8^VWPi}Ql =Vd]SRw.7f0PءCGr&GjE\lz-:/VjIȢaU7نcXM |Tl -4cNFOfC tc^H\颂βޏ֌II:4|w  $ $쿝"K{H( ꆋ4I( 0?Rt[CSځ2B}C<Ͽ;XC뮻[?{5Uw ~g82&}F5vֻ;g7SOӭ4M}>(x={ӟ+2@Xo;w^8z?oɲC=#|t'|׾÷PoOw;rq'Nض ?AzdMթԡg?d}p-F;p?߳=2[x? (y._|^ 8KW_={,Iw"/2\kEri^jNW_ Wڡ=NzeYqܹgYX-fC~gmoo lmmS4}HjTJܺ[ > stream x! F)\ #Z endstream endobj 14 0 obj << /Font << /F26 5 0 R /F30 8 0 R >> /XObject << /Im1 10 0 R /Im2 12 0 R /Im3 13 0 R >> /ProcSet [ /PDF /Text /ImageC ] >> endobj 24 0 obj << /Length 1040 /Filter /FlateDecode >> stream xuVMs<Wp Șc2ۙ3==( ǚ"HM}W63>]=n7QʒiGBDEE:?+^UW#]oSd~ujUk79$1oT 倏T!ijBO?jÙV)ԫ 1tڐ4.Qdn1M}yMjP~P*y|f=ţC>4u7/T-򏄝ǣUXh X=Y/*uCzʽ4&ԝWܰY|F-(Ya'xltnj ??\McAILa6 *H)zkr _wF·qPwP v4Q0M熄9Kzߖ[Bc )=JȯVdA 3D\՛M `ZRy"ASh1')\m:-ӂ[reQ'jB%AbQ} uc HDBzc83Zizp^NHIP0c c Y> endobj 20 0 obj << /Type /XObject /Subtype /Image /Width 507 /Height 346 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 25 0 R /Length 54914 /Filter /FlateDecode >> stream x <ǾfߢvQYVEmmOޤMZYJe)Be_n0Ì1f^n7$ߧc3<9=;(((H\d(((((z((((}H{(((_#lAAAA[7@F<85 !𡠠 !𡠠 𡠠 𡠠~|?0190 #TTajy' :::n;@rLOu|N#WʯP_M4_1%{8!3x:eGD?=F=PoΏxoҾ+#1#䰭NWWWx"$$A`ÇSSSx< #hii >x$4+wI{?B$vkė 444Q5??ʕ+VVV7Unnnﷵ-++G:h4zΝϟkhݺu'O {)333''P#QFFF.\t OY[[ph4/C|d<B:::8{̮{ءJz< I$Raa}ii/2}*D?@ j.bHaAp i@W/C|z~[ZZ2] f9K"b:P{˗DT*DC`e>|xƍsTjii ?́{tQpx\Y  177wh<˵kQ&՜||(;wX555XHNSUUu-www74v^H$gA>>p ?y$>>0;RSS<|=GAAf}||@=4v=/^^^ӟa|||(|o>qBfddܸqZ 2ڠ[>>>>>;DMLL썍2;h_~=;;- P~{{;Hz⅋KRRҐ;""1%%ْg ]]]333 NNNȼ->>>>>D䡤$ ٹ`(MLTTTee%@SMU F'|4ENNKpp0bp8}dXFdSB4-%uU34Dubߕ ,e7/{` cn+b[u;0FnEVQ{ҩ|A>sk+8avaת) ~nn.|||fd4755=(Jtlc7t1@Gt7|X dFb}^VE;-l5 y[_T Μv?8mю3_myz jc2J(CNRG;qKgN@q=#@$&;;ƍmD?'+}oF! :ɩt'pԲ\׷n*(( |&!tELc>8FHa1)v[K.Yo67[ojzƮ /wt$ET7+#?5?]K1񖇷n`j~yٯŠ^D&Q՗Xl4]qӡI{zիW92Nsg\,+]C꫈$Z; L{4uGw1!;7n4`nJ:_FeB=N[oj~;<> Yx.OW|f R*'|3 ,g!;/| G6 a*m/J ;Ł{rT='*1]C:g6ݹKs'ۂSf=K͡ }F.q>qdp51ZhHs0~\l6͍X7OGZ֦Ojr,/aɟ?4u5+` _OTݦVͥ; r#Kɛ;^G '5όO d&.cIqQADDEEEFImM#zc2k))Q]PK9:IQFk-OO^7C[~\! |#෻Y 7'2()1="X^ܞXUTywwVtD͹F+psqu}vgaiOwͣdU \ؿK0]զu.'љ=+ #1ztRwtQ~.Z8UYQ.^MGnFLP@A>YSЬ&ĘIw5NL}%;MQSN^aD>f4MJvΎ"<]dlGv]UTT]rnz`K_Zn75bQu%نNp|тM{?JxLk*KaW)X۷]]]A_G#2 ~i Qa!.1 =2bΟdh/#3~UǁdفھjfDiӞAX22I|ܼ*Z:;6/QbD7oQZZqu׈@G 6-$ݵ/%$!gºE:B)mg2>-/#v&򞭴Ȋcwp6?fK pppZ ki#2dvٵLXP`촕Z+/)$}58Lo9n)Zl)+( eM?ĭ_ `Ro].">BuPL[AM7@54?ĄFs mlA^.#0HSXJʎ|zu\M96 W:th\76R@PWKPOS3ySEl8c&-1)qrb\\hmagwf p ^<#ѱZϹy:SX~f3&gBcYA^4㞱3V6r}ꀢxj~֕ڝzqsO7ПGrDLnY\&YS i# ^_V) o޼O?Yqۢ4ZdBZECw*8JJq$y)q LOxH,g&l1oj׳Oo}_vx)äI,--333>|#w6^'**4 C-%#r??1Pd>kd~T ד4JYs'J Z߼%/'r6ҔF3fp.~Zdձ*MЬ Ӣ"ھO2=3;w!/Gu;MLH@u>֔7#:^3*G%E>+'ͷ Onqcn綈6t ^Ў{vFЋRzT%^^9Y4vy Ud%Ul/#0?}Q:5<Gw h7.’Me&m*/g̟MᨬX݉"6oi<"f[鬤&wy;FY,'.𶶃]#gf.DCbBꆫkcr=L PxU$*=q*J\ mYbOK:E4 bՒX9szRJ[ť_ ﰞ:xSFϸeNb?,2s?FGUMeı26!=S}4b奓LP:dUXZ~SR4әf%=#)q枿꩟DOzdddBBBbbW⢣+aaa!!!Ǐ=z4R9s8qرc%%%of4lB] ?)"'[O}o++IC0ٻHKH"!)1+)7LPk}nC ZqTFkL95户XPJt'Ԏ~_#k@菖UŹmzSlEQqNvI@ĥ{]0u7 L`!â2i"-2FPػL:ilKWStd O(5f)sYڳb$D 5͕VfהT x|y/0O~ӘS9w,xV%PG) Y}V[]Ma:H|y,3o "-Fw? ZT'x _+uq--T+y!Ӭ>@/,Xt&-\6%(l|;|(5?rhtJJʅ  `cXZ"7ZwY*+6ZDљg-7LIu㬚G0gwX_{ý{C/_644466ްaٳÇ=z VVV[lBťt o뜊ĠGk8<1']VfVN^I2NcIۡbiUcutf2 '7ʒC5&N-F궭ވf}M2BSx}KDe_ ߥ=>WFPnR@BwT^&!>z~/q-xx뜴(Ni=֎J^DwΓrPRbB&'}l3;itruEW^;f io+7]"ۚrrO Od(7?9IC_VwE^OQ5~df A%[m^%$0̎k޹2%|fJFM>nA1K?jE7iA>n.} ~t0 A#ЅI /|=׭́3?.̤]'!jŞ7-E\c_R~Qԓ֊$U)ٹ0dO |;;Fn6c%'iF2gZ^SCUMO} gM8mɝ+t(*k?g 2֏5YL>=E'7^^܃}\Vq<(6oRA2IgL GDiMkzGeF]vȌWY "e g*\ /RSTf8IHH`a/JҤ9aAkȔ;΅ .^I WэdO||ɫuj]\B"fΧWX-S3RЗ5N" +Jw P69ۦ%ODjmeqkrq?x<~rr22I SvY wLVxsL =`PEh,kh.ڰa|vCI˷غtƺy q8Szzzee%8:`4GGGo[o}deQrJZ /]r&&t=_C:BLUQؖu9Qj~mTŜ8yѼb˔gw=eN&CmwUO@y%#.ȭsU&(odzTY(HW3ҵ[Z[ "oQ1ܣd.\+Wn]%g?SYtRZ҇ywg!NysHtuJ>+nD;ߵb$'s/ۅQ'TϚ7\[vwe}sbfiiD|-ꘅ~gkQRR:NWM ~S5X"t!f2!ۮY"/}c!va)1!NӅ D$&(h̷ө$*CP:;x 0Ҩ[H )̧u|j|l~?+%blqȊ]4cyě\dě^`hU7-6/Vp(ik|g)&cŜ%{S YogoH;{&=t5>aaUao>[B _>;m 7anUjޑw<2Gk{O~N֔8sW-tfY -pvvRџ%#*mg+X bOUӞ-3k1DFM{/9^jifvI4M=iKtX/:J}Hswif,0N-}yG):-t8]"8m}ReS]AX.n߭6|fi֏>\rtciL}?̎_߳k{=T0^VRM{s(XPkCi\\.OHeq+}Q3i[Z;7<-i>o0ݡG|{N}0a% (ǽ8V\+NW>[gATA#c_}I$]:Ce_Eˇ8ǖ1, L)di!v[=*?'Jlo {qFϻɷPٶns ʕ+wmDg9Ϣz[7컚SRzrʎ)oMRTX2fTAg7 [5W]c0>+ \o=XͷMB-qMI&6ߣ()+sY&o9QJetFL2ZTųn#8FRLN{E[4d]'!*>coI}(acG)/&mNy$5i|t KyEUEɮFTUQUOi`P~q8,9F%2#{MiB#:V9{ pjo/'0#Wfj ŸEyx::ݶp?Y@IZ&23=Y AWc])n.ѽADVLadUyNQqR’^ +|js^Tל^3L#||BJ3ap Fx8:#,R}eS%=dEM+*TUvtv\po~G"ۢE +:}%uYtn.j`yB(iW)p><k׼?^_pB[sen33"O$A/r<3[j Bq5݋GDm(=IDlžӷɟ\;88)ZY4Ok'A?0NN %͉ <wzW{չWdzz|Mέ=~?FUۖ K0Ab.2Kپ|0/lkI6rHlMnMGFSQ+^7VLTwձ;VzSœy8V]u:(~^sqVwX/ew7zJܜ\3bBtA未% ) g|Krk/;\k)A(Uێ\xn:/{ƪͿkų/@L-{Ošx6NIfN_{s'#$Tx/Lߡ~mw+ qX 7@ ˪s]sjk}8Y ƫ7o8::zxx444|<Q]Vד֖԰'eNt;O[[;'rZMҘ-A<''YYY #H,,ͫa8ٓU+nL9MS| ְs3jXYGl6?G+=jع[a߫S#[9F^\u’g |xxt's{εf$Gޛ:{y Nۥ B]$űki;WH=R=,{0tЇJԟ7fٕQXdY)ϴөiȦ|F z2'!TsE0jv~_6K%mN2=~Iץ4:~p'cqK_{̌-'cQ.2bGLl.wwuae'!LJ-x?]eΚ< rN-8^g =N5 8)c|h;ʓR'JuFlK>,.-;g YK$5g77Y_ g`{~z#PN!WxW/^םU~vMKKwrrPByŶK~*>痝]SSL]PVCbA#WK+k;eEUuԶFFi~uӘ{⢒fruޚB)+o.=-/),`{ۍV=[n"bI:1=eT̫:< [R\ROhAàe> y4JEV5. s݄L2yUqdw/$ Fi)+*TzZfg}yYA~>_cP[^?r/(%<":".)Lm*/(.ku#7$Eܿ8y [_$Xt7(4M5i*0sUz!٣g{b{BL~񪃃St]So߻}OPx%CnB^ƿz:XF}0ȿW`NiUm/_V4Nm rvr񿟞ڟBy$,1#SWXT3B(˫0-K+$##1eo^5MOʫ 3k6/&4w'5V*Q?HWqo0<-3##7++k,Ԇ}X ٛE}RI(~0… CfG.kk肂}ǫZ [&dOSm D99_GTtnb1۾? ob'O6?3ڿ9 vLLӧ}||uƌMMM7n^p p//oh})__ߴ:9  ENW~tHHОs !!!!{⧧#w bA=<`/f#K>{ /^||(|o{-PP7..PNyfbbb?@CCAC  %%%IIIG lP^^e;@CCAC!jo޼yc0@zz?sܹjdhttΝ;o߾ QK. 2@R3Sܳ5877,--AF]$ qDСC_?''g׮]iiiVx<˗>BV,l3ڵkZq*Pׯ_߸q#D ǙcHV=ztaEDD+ *A j$j9Ba322tꀜTTTӧO ;v $0a¼y???Л@{B$37-_\YYy lv-FF|aDz5 RŖaےN:X|dL`0 XgΜ9~#GL}=zĉgϞvڭ[@| ^yT1PP\)x#cbb2334 ǃ ?++ to޼y/?t9{{{gggooPVrrr>3>tFA! 0HE#()xqW\Պ4?~BeD=Պ8TRP .888/_tC"KKK߾} {m_>000<<tpp8Bgx3RWWWXX>>>0;[7x%/(PXP:PLnF;RKյ珞]\\FdKMÇ ~NOOG&Y|nIII я?~4D={Hrrw@rJmllMMM)=}QQ*kS HXJ @ Mr^SRRA!;;D+1t)X,HLKK?z忹~bziok\#W5^⚚&f>0@1}TUU_ZZ br @=sq':G@:H&h4HxĩtiiBJ:5L<ⴢn W]] AhƐ'~82  00aIH);ί'lnn oqFpaA{qDWSO%wkd!@(MTj?7Gp Y l@';# $2eڭO- ȪOGva!tV}:,$NeK'9LO1>_m~Pð1#O?PPPPPC/|((((||((((||((((os`C1G~p~On{fփPuD\n*By5}Ҍ| 17ZZZS@A#]B{#HR`F|IBCCCY: ?+WXYYyzzT߿ֶ W#hΝ;ϟ?кuN< +ԩSfffNNN7F.\0xC9YCyZ&/Butttqqq]}%CSSJzN$H=`o!7HE [|H㑑s[ZZ໧N?p@aaaUUo,Z6P588Yp8#(<<߻w/x,//'_ R NNNvpp#/7n:=PKKK||(aݻw`0CcY]7䄄@CCAܹ3-- Dt:[npCVV>2D"? k6ɓ'!! 9wؑZTT!>2 2B衱x CCCAC𑍫 7##ƍ?ʠx_ w !!!!I$@hbb"`oll@ }nIۉDbUUՋ/\\\)))Ȗ>;P |<_^^ꚙ9HHHprrFm!!!!!%: %%%Ci `e*++ Jl(oh$v0:m}&}ˍA鹀J z+k`dU|thgtommbހCi С?cP /?uu~SQp|mk۱vX+dD{oZOfgWuf[SV#Hxl뽸:gV3~ AG1#g)rrr\\\Ah &2|&6#OyFW`ظ:â|%3D84",iai T$ה2bw=pTQ8ov) n N8`TevPB1ߔt->>ډ[:sKY $H5557nܸwGh#9Y,~3 f=ON//GWUUeff^~= S;;|eRfEQڶ1'[yyy>TB`ceZ]t{_ՄOMMNm}|{t3ѱ%5ض/t<{f{~]qÐз9Bc˳_gwUzp˦ Kl}!T\8ud5kם<%/[&=#E`@Gw?<O15>E5z@G IKR_vzzSgW6vM~ 3')?(]9~!ZBυ)u"`a^AbevP_E%I~Oo`ޣie; ٹqs{PRБ:6".qtzS)?]W"tp~#3 ^/VR9i%a9 ٙ.xseQ=r x So~QJIܜ(Ш]ݓM΅]cE82Yjo3mw(y#<@Cc沽fdd/^I2Z[l1xR}zڂWxE iu9vFIIylZruWϣ˴'j5Z1׀3|#=fMExk%j]bU"m6 $t9y\AT]Nף蔺[smܼuI-ĩ\rn:r5z`" Zf 0)? $Lk`wb|.iJBro #1iRsvl4w$g>몢"--ʕ+t;[r/6tv{l{QfZ{UImȗX `@Obo߾ꚝ 0>a`ܯ~mMW q \hswd%C}9e5ɈlUد:\& U 3'J ’LUٱy  BЊ׊F,_:jLTXpn&鮵x)! 9N-XMi;/oy6l%EV#vY4c8]]JHfXKk!Ϯe‚cubXy1N!dzwkOReKMYi77XGoH/kAoǗmט nz~rcNӟb jg9?= o&&45MEk/SXn[Gg rqEeֶ&F\R Tvӫjʱ&hԡC&1\Gmo]L}"7'ɛ*bgg3>ny}O<Ekc C>3H_OHdU :|Ḟ''719{ 8uDi;vVeDS,x \>Z$ "gvru2'JrPGXeVgOhPJ1/Vonn捽=X!6Zu去F&*zVkTQR&KKM]p%ez“uFsWf1<3a|Uez#&|phӧO&Mdii ȟï=9b=UQWj)[9=&%E8X' T8DQb8QZ},t~x9q͕;U1ߗ<51w"=&Ulf5`Y32l18#cR#{M}Imw-&(X^௴HTTyE=<ۑmbBW4lQ$`?*)*d~ Oh<\9inn^xrow;E|K8vܻs%4^+*?ϢS"+2e{I1]苍҉wg>:[Gkq h*>W4UoSi}?cd5mrGe =5N}K֘-1;Jg%Eo,61#q4:by>qt?#$7s?$ }[>חR7\]?Ga*]cǭ&Q UQSJXldϒS~ZՄБ.<(MlS_zΙmUڲ(.JP}2zƥ,v˷UauPO|@|`y/~A6:j*'װq +/c!{gsM84C/Ia4VO4  ?}4###_z ^ 9~ѣ̙3ĉǎ+))3aMhlI991z~3ܖ_]1 6(H!gn?޵HGZB5 I_IAtWY7g8UZvjV ˠ2ZcΞwHΩw5GRS|ѣ ݲeR...%%#GXXX|7_T$]֘8Zkš992rrNtʽK-};GOsur=\뮫>vt6~P8y=V4hwjA5RmmF,&7gwnRH[""({j`FfVvv6.2r༳rb5A^[=}ki[GqJyvtU:ڴ%uV:29HdCI+J߾ ri4[GM{[iQ֔k4EK|Wx"ˠ@N+}q>'3oH/j"(!fv^Ε)u O6Sm6jqsJYDx\HQ+ Oqs,\X@7{Y X.LJxn,n;uxd&j8 PK/i) (򋂦V$Jq݅!3R}6vQ+.9i֟O/7QT=ׂj_jN?| />n8kiKDG^CQA=kf/5}36455ʕ+eX,D"fiV{{;󕕕EDDA 88ke K7r}e?Ǎ`"w_ ||)Y?c~/7ufL1)BNn>jN㲢Aq lxZI:cj8. Lk:^kG;*dƯ62蚷Cf\ J()se8>cTQb(}Ҝ5X]",0kݮ!NBB{Qz & X^C}p.UpJOn$3K^3P7;t>jQYh$w9Xpڦ.QXQҽ_Zɩ6-|]w&Pno/=-Ez۾Bv Yl;c710X(x{}f9/F@;ס6Rs4%8V,d~tt42>6+s&\w渑2n м6WUTRRfJEm D2XtA/OmeVH$𭬬 q::p%|@ :.;r"r#5/\|2DWXu3@Ґ_J * cG%}~4mmQIwmVMkni$9v iVo>yv,|xVVX*$ 8Ag2=Ox: T>>e⼬۔g=B-Ӂ- e̬_wsGVBp1TGI̯d0{TǏH+kmϞX *Mk\rZanY|+6-j4Ÿ,6~Aiy3 xb sĵ#cG }PlR+r_:a!m+yYNm# +qVW,[(jZ[+;sVnx}~n ۀ?CRq]Y:5c&;$5GbFI#חeN]lY\پBFHiyRJcfȃs4W<]Ti._H7].\z߳gL3KںSW1;bЊV9e2uF>Giʩ O±NSuniVַuƨ zђ #:⫑ۄjz惯>evZǖGŅXC:$sE%ɨ75ji @&ુZ}crd_;?O RPzNoҫ :vN&4;1ȱ>d("y >4 yMNMV8ZP_~ãW-M5f-{PՎe3\~C#ܾtt"?>6=ӝ2OXXU{[@W-#Ӷ 2TcƮTvdyLq7=^TR8i[qqqEjnxhikvd Y1_J/`&O~phۓS ͟,*2EլS+ɢI[ub"G|M3͞3W/6xiZz;hwuQpm\ܢ+.<ҬV*̪)҃;wu*+Z~a:HRtGUSf њq 8$ cAIk&+rq)ڼj[=_,mћ'i>U^O5dCfyv=>."8nQjyp 7 q/8>k'~43LuF%_m~QfTidSۍ7 mdT1/0.8ns=*('f~1vʇ,8`\17o>x6R< gj/l>7Nmp6'ni)]-b>[jYls^~l30 7ɩj@;w$&&Ga ȹNU8fSZU^Ȋ~4_k8g|^KTX^ 3GH\j1ܡ+g ps?%zL2KPZf- :l̔M-TJ}Rb=^%o#U殏Õ/1<-Hi. k`2H MݟC`r# NFvb'xO:.wEb*K^ƥ#**("56V?["Q޾4񊜄MpeL+upj'T2:lq!n*i5L!*ëh,n?κ3嵐}`&/תNu]]u<˖AEwMd47~C k+>3r<'CXO7w6^?NA9ȵt&Ue988x%yd75[6UXrfQOcP-o 6}5#tk:rE FΖgG)=h]RML{j~JU^m#3vIuΣ5BȌZ?snWQrblɏy+#;|Z텢4}CZ*-ntq:x:,py =ӡ`x/VJOl+sc(d{p6OM56}Y\vg.R_]C #(i>ಹWno:s ڪKӓEcNxUaaaOVǞܾlъK%6ww#oe42JjYga5өҲƦfFS}A^nVQ4UxlgckEpܵ~/XCGMin.Q JI^^1Gw|L 5u1cK`hTem]s%!w%e*RENv1AIK}$EZAY3,aE.}}˃$jCSL,L{jo2XB.5ee}|jo=-^EOƠռynggs'&) {K3jm먤 G_IYFvxwG[+K>~I-]ZI[Uqi+2qzX[{<~:J[VOqete{ܽjlllb󬰂[BVE >@aG<25ڹxcsخc4ƿ.&)磄4J)ĴƖf|~ZfV){*3-lUIaZZfp9*2i湭wbpOHHdff@q2h؄6~ЮۋgannY:=N.t&",rSϴ-Kqs]=/btL۴@Q>xEʉ*1xtDQOemR i&''w*)9ci)Ԉ33Ǐc_}L ~]*W^o߾M ~233]fffޕ%:(QB5o,ꆅU!NΟ?)22KtPG~7lDEE?~z ~ϯPG~ AP?q℩)uHHH8w˗/[(QG뀏 )dcc{wB+'Hׯ_@o///h])fo NMMٳgc2@ss| (9|,gϞxL-P~3t͚5|@+G|7B'zȖ+d g!Y%iiiO>=z(@Q`` ؐ $ڻw/3'&&ve{i/Ձ@Wŋ...kkk:TAAA^^~p@RÇ={sݡ7M3jX6^bgRPPYx :tY3888)) n;!ȧd2dP󝜜n߾}ڵW^\bfffmm}=???hVXlqqq;#e3>0r %p Kj,X6BqM(VAH)#b+Ҹ'K4 n?~[ڝZÇP=<< vtt|꽽={\CH$u1g>ԔeffB˗>tuu~&YrjT Bˮ? LuwY׈%[[[ k~Y꺻?y焄dBӇ:C󣢢 hP@@$+hG?~旗]?9 FUVVƆAd~R5 m&( 0q/_Z uŋAAA@NW XZUU3 EO ~dCJJ DЕ޳h)*** H ҌK.?L(Gv}pEFFB9_w"Y%^@=.))!Hc;2 (4IEEE@\\!A@=@lP|1mRtLp8H?)m3iB,~CGfqZVqed2"NG3z p * ؇|TSKt YvPˏ?LVWWC )CNV?6MB$ )F"zNB 9@Z 666Bp}IA&huP F!CڧT5w[H:-,Ȗ/r#b|M؞~`eFc|TPBB *T(QB  |PB=Zށ&25s˞Io:N[B/H&:ձ {2U:l#DȌ)ۑ4? SAuC+ ~OweF(Е@w"߼y#Ç!'O"B=p&Hܧk ҎQ'y[ eeeu1ͳt ?7o^pѩGeggwAcc㼼<]8nW^uZv3gXYY9ꏚ7oIo|}(> {]A_UUU]]GG{o~~Ǐi$IP\]]ܠRpz w%(Q?{, px͐#""mll JVA>X.ٻaaa.\@ >cǎpRCx{{_턴4hY^~]|Z\QGСCEEE^#ӵ6Cx}#",!Nhhh: BVVֳg_(Q߿?ϯ- >PFAmaa NVɓ'wȀ:lCٳ(QBǁwބ;ÝhY###ȨNWmdG ~ݻV$"톆hwDb!99Lii)Bʂ|>4 }GΜ9>*}v튍ʪu/X[[B;~hk0> |P9#WꀽwJZh_hC(QGxFFF{CCC{`8@)))nI@ |~SSB)**z捍MTTThiilIs>*(23[[ۤtEDDUxxxqq12o> |(%:///kk댌t8Zׯ_VUUiRQA~y%hn,2X葞 $"mma4!L[=  {{ zСC#+T_϶]2FE夅[۠{fOY:G^-d9m^jWگgo]kwV-a5 ({FFTC'TVVO zX,Ç t:te dUDd:kyoM+e1{/$ϿmZc=l "{ ~Ϩ~>AULz3 ~mKgsXYpJ9 \S০ZUU_72I幹yd*3zQQYʍ,wss.t4â |%EŔ/YzTRi>yWR^U5[[}؂n&=z]M h>oץSq8bCf>d=Wl]/݀OJ{1^Bhe5NB\())^^^_LEgP])=l4״m<RZZt)~wO"yh6E} >؈Zg>?oN_wJ+lkafJHPhNIEcZb^=8ϮM<_Ǿr1;oV{<Җ'F}uTˇ~ $F'F#%]k׭{Xbm[w?/Nyh3"?塷ļƦ#n޺yÆmDũ*Ě?ak֞1rnֆĨ7^^yǣgaZzo>$#Uu=%yԱ!]7ܲ`.J/l lTO`qem{4dž{J7:>#m7tBH"ҥŠUEyE#߸tru7 ߟ_5ڴ~ͪU;Z_i9^nȑN}eYIćM0vpO/&uDwӍ֭Zf+ &jY#W-&嶗)Mᤡ}AUkk3]O@ <>D׏7n\a_mМ>40,pqo,> Y<˕~k{M $UΘUP?HBX@>#Oh?Z91N!=W_{ϜtZ!mH&{ԇrq<+<~ڼW؆Ρxhg0cW"&FXnV:B2Õy 2q}RImkgO^46cBm@Fn>Jv4Ymˇurc6BqH5̔s#w-F_TY+.Iz7g<W[&8DV\Emm>GOJBL%!!!Q111QI Ȉ{$Ce5\Rj'NnxF.xIoM*|Pӯ(%9>0հf;kP8icSezq)#g[6KSݞ7!=j  ĄYpHPWv|"1~.6ن^U\C+,*/cNуǓ*r~ 18Oko!ٿI=1)MեD[gzbdA3wmi{/[4# o޼ rD+rߞݩ+)4hײoF\Jɩ}ݼӈ׻v>/V1V_Uꓖ%***޽kkk[pϞ=d/Myy21a!.! ^'?3nl^xy9i~aY+w:yPSu&s asK;+eu#d/:T6&<^3WEc-'NTb69>DN* -o]6brj(I I?keavD1!^+62Օַ楇FSExs]+888 j; [),:yuk'W0Ps߽5TY_uD5h2O-z^wx ҡ*K.\8Og,/?hdp 𫫫qqqfffбK,Ϟ}{ӭ# ?_[~ߞE0ۄkiZy(4ɴstVy僴H$+>N^xp5jٳgIIIO<[W-$ub.TX]6؂}3$%\,!2OP<,2N**I/V)7żyxwo ?8ա*G'Qt@q+x"ֱ)^|SD5ݞ'њ g5+Ɏ_y𗟼_Iy RT} =<ݱBwe0ut<0?4;.%&XWdo hD&53%ɥSO+LgٓPe9eU>mq/;ռ<OјUAn&5wi΀8gt fi̡PW>i%syxE 70jב{aⱅ|^JY%ߴ%j un !5U1|3xL6 ȉ|4DHPfZZ'*]U 7)qr+mB6b<i,f-8PZ3{nB27"krWjp)B(;I/JY>2i!3׮]*}lLT2bPYAaW,}S}tJ3;+k1s-|`6kS1q &Nu``/*/^˗/#"""##߽{:uj93g<}'rrr^)D?lQlIjnO/l/չ RGU]K S,(#)c҈cupX7O.[;s0Uנh i{u5+hx%/_Ɩ4UGU&@kNWRj/%?nB1U_{0͗Ʌߝn ePf2@\b:]Rnp&2iNEFBeΊJ0.' ɱ[ɭ &}+jJ3Qa֜2rEVc]ɆܼBaƮ>AnÀp111&&&vWՆ389El<楢;.x2> '.8}1 w.֜ta]}QmYaoܸaÆ;w۷G=~8 b .oݺUZZ1kȑǎ;y[scˎDbiX3zJWv~ 9I}8vgk *+VJ*KEj1oolF1R Ԥ/{^޽YYV_hߣB6=HTp[މI)))W;g ʍ½ G rH Vt‹ Ěxl$#!)3%n.Qv>x ?=ʠS)Ę7nkMLt n-.Q mc+! fѦk7/`ld_Q~Abg JUcЬ=xj |SSӯF 57-'zνվZ5M]jxJwML[saVXUWm H߼yPިnilWSSʢ&MCÇ?,xaPzrY[\yMjQ=OPj_`y^U^JGT$_)`JȻqa98\v~t~xAt;sU5ln!'7hC+"2}brax0xWwoZY{iWN:NCDPG$#,6o̸m_TS4J׵t*#g.hwakZ >Rah(7WNXPej]n}s!WJ~ٛp?wkuFMvs JPHd֨/ߚt7z8yswiq*,i]`lb_nЎuMe թĦ? d2 ʣ#Wݿ9n[cp14MUDDRQ`þ')9ч .vakw|%0&|++Bufii _#? >H /_ r}+V "P4cDgQI_}9/v4s[TR+eG[zUӚ[Iw"*/1Hkl՛O3K}|3' NЙLezo*բmY0{8/6hzPt` h3A3k"]\v̹4,Qk+U#5JuZf'( ~Z/:3D[Dv%}| mM :)_lPZy}u;Xq‚f&1ԊN;}dJnS3CJ=(JqZ.%{pĺʎ*Ĝ6᭻w?_"|6OT\W-|I1IMđkR+HCoffeSۺ@))WRwޱҘ`;OG&g0UR5x  -R6vtt;?,GtLǎUNYFj鮬~66xڤr“p쟥T2o/=7GZz-}1:$e4~§^^YGTVPXWiIvA|4Hjvi"! XP}yҚɊ\\6/khW|&=i[qzO;l >qqУ*~^Fq*[Z^.7Í| NCǫZ E+f yQW_TGkYoJi.ʉrh "_a0 wo}s ͛<򁶍OkÙ ۀϤzSde659[ZJea'F)*r\s . MGcr*<Ν;EEE}QrnDYVՁ/4/)e)֧+H;Z whAbYj=Oɡ Y}k3qx|}S RnjDWI,~ipet:%t`1A *)O!Rr>zbBjS:9F"=XsȿFI#G-ޓݿa ?ʒq)ň uͭGa8dT) w"'!:1\b~)B<ܪ; Uo98[\{[jZM=G *Z?KolnۦíLny-$E%e2õjS]}WesO%!c F@]a%%ЇJό*)A/O!-,,}§f>S_L9ui`T9'd./5 3'-Ry_{}poAK ˣȍ-,ZU:d3[1vbl_>xonnR]Սn5gpxzGIq 6bN0p2#0An cU54Vy,\'?撣Ķv8GU^#r׌v˪h  Mp7Icg]aBViР'=U"5cA[[x91S͝S`N?r-vyEYq3[?5ۙ`41WGsJz+&M> *ssqho=UN1|Jf!""n<Vd ?ܿfO撝{D>*???..Cyas ,6`b=Okjme}tu166qIʧ@}ؙ~ X,djkkY:拈]2Kw< 3Ʃ1؟W\]5mĬiajͅCe%>rGh +G+<0Z$#nPImĨt5^I!2x͖MCYT[Ņm9gk͈~1]ux5>GQAuQCZxT?5ӞRlH9̾]"`2}ĸkґr!<{ * =7H+tB`|7Q[q>G3谄PDkjbt>T%4|trљuӄ8_0[U:kT%)聦b#l^%6v>=Ċdw{(M>ŐֺJˣ[Ex?;]e4G#z~tth0Kդ|;cX'JٞC>\SSEk]KԧWʮ+i~5lkolx3ëj$ev>6>>oeeUXXSU'/[bo} 9=%%;G+[ EZYXtj.T_UTFklu"^;: xVPL3*k'nsy߿ce "&< ڛLKu5yYYi_*==>=[Ol!eWQӮ1h5CuIʀ:CEaz:*$*,#!EzREFz`!6ꁏ_RfAKתsVU\aʄLܫ^6;򖅅@\#](ew<+ t(EQo߽#P)!OLM v."߆-d>iF }cme(!?R}< 21UʞjLK+,<-[URY<\L'ŽynkcꝘklmm)=P 6!MG0+bY{{{wzN:uv9:H53m˒}\'G{8fz`>6-8oTErgJ *?QjYT2?>xZfbb{=F'Jjuw 5""X,W#"c/QW5JſS} 1ͦiSQF*.|&? @MN5bԳ9Igf?VGS罼nܸH鉚T]v!;/^\z9@ Pzh"* %_h%OPSoSAIiGF~P#T|˩„rZP&h`TB0$44444 (//Oէ50-Sp8 ^Sݙ9fEfS~}!`?ʕ+O>… L[(Q֦Ʀ?1?1M?پ|!x񢫫+PwH"ܹ p ot |,wS}NމD"ݻwFFFwblllii᪪: |P9ud29777::իiii㇠ϻVWW4(QG}c ?...@^p\.>W> |(Q:೗'$$ Ob/^ LII@NQGOY իW@۷o_k׮we |Pib" uaaa@cccϟwrr(QGMC 999QQQnnnǏ;::B󫫫(QG}C#O8ajjΝ;gddˤbd> |(Q:#l޽ݻ I$Л@i4ZWf[SSSwpY {<::zB~mm-~#G@~ٳ'>>`E& j#@_A ]f 0JQA)߾}{ƍЉ o™cHV BZZӧO= )C@06$$  c#""]^ GFu 'Е~⅋ 4CUPPmA"gϞ}9wwwM =P(]|h~K.UVVVRR|ʳ4? iR"ء,{{ eSl5YQQ:<ݾ|dJA!֥KN:uر4Ǐ?}˗͝!>GЁVF577wstt46ݸq… 'O_b H3`&wցX|;1(2{‡" NJJ***B|t:L ?99:|''۷o_vի'##+WY[[߻w,[\\9|錀egg766~_c R ցlܼy~P.-8RʈŊ4R0Ǐ}bl/# v@ ~!44p!94zoogϞ!uuu] ٣O555eeeGxÇ]]]>({c:0j]5bo5y@֖Ʀ_dO<9!!~|df(''(j`` i7z ڑ?nxOr@΂haaar37Fi,__BlJ~ B܋B]}EPP@,*е)|$VUUA @>qqqzS㐒yAAt%솩Z HR4:zKO1ʑ]:=?\P0ݻHe"PbKJJH$ؾMRQQ?77bn(|HP9.9_@ $8wEjZnK_PY,Aծ~\x@<2 Hь^>;~BJ!UyT$H."tBʐ~ySŷՏ~i@ "BhJѺH>S{C!h,A8\'h߱3)@RI$~*Zx:T|;Q)w>UDN K=H&_S7g>Xخ7*TPPB  |PB>*TP9vO~ew } ܲgқ=PuD=ɾ7_u*ށ/@}vH>2#k{v$MOgscounݐ_jC/+3]!t%n7o>zȧ'CHɓ'sDl@&<<9 6v_TI~~~YYY]r{,6==ϟwtttQ?W^˃kQvI9 ]̙3`,=+++'TQ311qM#<-[WWWZZzmkkkǏ\P$Fi2kkkޙC sM\DDBJn{ZUij6!$,%*$D}ߗ1f01f07s7%~9s|߳礧_x1;;7777>ѯ!=R($܂!c>>>> rʻw|𡱀3$N Zy_x/--A6"##= u'^aa3 vvv?:,--!JVA>X.ٻAAAǏGGG ws䔔@PbX877[nH )))ϟ?ǂ> Cyyyvv6|  {BoZ!ZYYݿ]R|L`>L9v>> ǎ3//F},d2!tA&@+iii{GuZoo#G ##nO>9peH'%%uM><$ Fu:"|hT$@GG wo޼9** dUQQs6͆vښJvM>ǃļJJJtzGiC>x  iӦwedd} M@x!tpQ߾}hq:2@G6c7ւ{ccc-,,=zԑ,ʀ[>>>>>车  |{xx\~=!![HH~CCN/,,|򥕕UXXX,""%>>  ueV~EGGGGǖ9deeZXXueV@&X[[?Furz#?]z.p/)îc7'PKsSwF~97566p#(Jzz۷!ʬ|Ǐ_~[fUQ9h&u.Z+jIvo^Rbs`gz_H3Wqmk v\t%9T^F>4V^13oยo!1$_Q}Lvvvd2+"))ƍ߇.JeX?V g,!!OV~b;ZSIwٖܐq#u s;r )tƽNqK['~] '⽢ fF~17?QUF$&·N2jϭ޿m騙RIuH_gmm-?99ёF}JFX9)IiNVnl񿳳3t1ݍoI7ѳ5Q[qy59wMNU]:)rsq#n>b~& 3tަ3Kv) mQRYmEƊJ_;\4a^^56Qg O!ş555 ...QOwgT36M{hwp!:zw}B;9joee=4\hῧ.K[l 9xE^O;/v.¿莄/_4FVlbrmњӆZyn[_XXw577O}ROw<=^w 97&Ӵgll&H666)))ݥ>Vh ?~ftjA]j&osXo޾nU/'wlt[*R;dhyHA~#'8uW~*8M9U/<wnݸ`Cbk 43;:)%mocs c{u+V~IH5?J+HpheK.;b|B_\vlXGv'I§y=pB`r'/"jY뢚uҩ5 /?tjjXMIa/\=|kZ 1kWϜZNJ{}tߺ+s"06ԡa҂_d0+.>r{_Dq.뙃K/^|ʤd]?wdD4t[fٙ@?⼭KjQ%[Ku2Y`6M̧nkwM\vK&]1ڹ|\+ɧ5G8Ff0VC+V,߰cT./<7\|*S +1Kߠ:˗,_  [s+?>7CV9bLa|*s<>Foݺemm yoWp`&mό3XHP@P'Jr&D6[Wq ^8A~ 7]";z?Y[9TX~jB8a'*PMs^pzXh%wd-)ݥ^*.o4 G HQ>zɃT^z~-_z*)ʭ8~7QiÎ#&-|RZLn qʞE4[u`) "8(5r]L~tIq5)<, d4HQFsrL1ZW (H ۧɎ)o-sVJJMƦt3_:Lk)YE%SG wYE1G+iLқ7IWkX秩 XϢ>D|K Ȫ JbzaZ¸$joZҶ1lٱцNL4$د8Ě]w87RZv⦕ -')wя *???**ˮEYcI5v1oIph;> b5f%:9X{2Vft?%ӘB888ܸq#!!48aȴ*s8Q~zlǾ3yÝI t|hiE[Ku0NPpwbvq{q=T7]p2^HXES{ӚZrxa>DU'q5y2\]ﴕsIwOj&H8java mq0A}Ȭ.=9@eWF]m-E{Jg5՗3c Q'薃'޴H " ;eI4n#-s" Z>sp\/Ap~Vᚪ| DvXѹM?I 5JV]ǯsEEX<2-M 9}ٹ=#U:LK*V? "ZEx>O9oJf9y[LȂ|vQ$ YD.Zu|QJr8AA+k!zqs0o]73DI'0`sP/$>q}qYn(dz8<8~X3b^c#dќ߾}Y^@8Jݫ\FMrح(33<Օl)R w\9 B(Q5sԄDWv &R9'Μ*|A~UU?22ŋM|Oz%97Qrˋ<(QJqr_i Ӯx ~LOwҢݯxNˋ ^?e̢- >H[ hb})={rPVV>z(?..?OV,kb9Hoy&)]]qY"j|05MWSEH\itm;YWc%IF}$4o L)ώx7vS>2m|sjάT[ rO(V{)g&i;?c6|$p#]vcD& s$=ijW{hATuvYa8V8@y* 'wx,5oRw:T2~~Zv课D/Qh&,,*2(?B /,'=3KiO}ZmϮ%xᾖ> GSdPP+r7N /lo];e>11R}1k2P!\  cjZ `j)FsE%ڬO9ST?y> KJJOJI>2o ~q $ep櫛:j ].?sd#fUpO]AEbe[_.㢟=t=ero)b;,{TzΙFWc((`]'V{qo "ۯ~*VIlZϢ\o #i r:_&Qf bxaUo&"IoI P 95BK)mWU%uP>3 Iu^>A$5Pf §jgW RERu?kE7 {ƻ^A7Z:œv \uf\=-V]l= || ~VzbuaR8I[H nŸ9j.Xi:9vsM4j?y6s| #?iCEE˗Ae ܫW#G^rC: UE9e~z8և&ޗeOQZⲧ7K?iO{vE9)A46wO¯$_5(w&\x_pWqj Bg/;@ŽJFϧ) >VUѨOZYs` Q-8nF}׋ƈ4aSIYzNE\$=W4qI~-Ӝ@vm0OY19 f;Tqj!KxcN922!.jԧfD<+:3 V7 ܬcŸڢoAڃDuCsFF` %Ԇ_K pwwMV{XlʒEoA 6>OVE Wo)YoO0k~p+e_Bк<ܖy3(Ja.%r V97K+n޼QD-"hPڒO-7G ;7¯NV"-M.%ŋݻeβr S$]Pqz};˧h-p8$>GInNnN^QX]zx۔Y6yؓ韛uvDwyYf#u%YYm&9Z]Y"q#Xk!  dIi- +CUL}OJx>{ɾJg9mbix!y+,vyT q0_˨aHOPSNW?͐NK.>!FԢA QkYr -u)AW Ҝ]hvsij0}NJa<}l{E7)'a >Q &:`q\0MMM޽΍WM j֜4aPtaf-^k|YO[($1=Ѫx|'M!D뺭%~'# SQИ]rAi719C&mK#W!.xQH59.v -yCax^;MqϤ 3G 1a}\Jb*69@oqĿt=9PzI*~K?z$QD?iK7;L(Գe?{]r)_4;{mTꝩL<4-HWC'$~ #MVW^kl=YIh-# RZoo#A Ƃ▎'SzY_;GWQHDˌI—^i|ծ'kJ FZBFD >J.0J|j(^haR+_jmE ' <Ӎ?d~a-[:{ʸ֩{OTUv=&{FnJF h:S /?m[3v,Hÿtr_‡ziB;&fe,)qYDtežKMyfM Z7<-,,Iʯ̎23@72s/>=s20;)·@S-OMY]r!\>& |῏2rNTĭ]5T:A'ަ(%.Lv ờT< 2bу.KT@xT:+\^J\Vkޓ6˩wi8Am,r;(QRW@ c9F*4{KjN"xq9 EFO>l8ؠ5 N_eSK8(ia!ydV7vM 7C3/lS">M7-58]V bTd2-a_[בe}q8_"UBp"#AURX_@GRk)*a8mP,n 54lOIq*Sbk;ZsRHNxkUC:gT/^_1o`AQTAcBxU%g ^>? _JGyĚ2^kXW@wfHG݄9ܺuUfiLdEX߻ F:ˤHc,p꣧Ɣ,̼ JD^m{0 c$1pgh2v$qh1ք^ظroKO"s\mN R 4BBCjOwߡ~r:]z0Nv8uUfԕ/ L. rR/ËhٛOc]*'%(@\3ƨawQ;xp±yBJ޸sˊbY xE')Р=KMXj<5a!AdRc{ 7wݞ( }xd(/( yhp8 븜Fi+hZY^kNPw2&{ˋሲsVxB~`b@"J9fjxZ1B8q3\.m$2n̴EΏ%پt+ &aԒ7_R=-_70;bҲk8>xyumj,M ꤘ'>OsM9Nv7Ο71j6}C'Y? ē!ȫq{/jҜZ" G_3a)ۓs ގ.1i+dubDkNXUB6/2ijS 9uW-]JEA~3is [s$1u_kN߽JB'[i[{ e~:w {ٿN TJcfŽ*O'#w_(y77K.RGm(x&$SSٳgm0d2>}NCeNTV38M? *Jl<(.)%ױ;3i_Ԓ :R;yC:*=B0/_|w>  ?o-UΝ;!cCCC;D  I idee9;;yyy@{zzB>UUU}=HH>8B-22 ϓH111G=}_\\\QQ6cㄏ Z4 jƍWTTnذz~YYLq;C7F]B~MM rcd޽ЯMe˖(Uee/-|̢uuu  /^ R+Pfff+W411v6ԁ=ǰS%)))=jGB;$600ҥKR=[9CbA CWٳgo=_~rrr}4p8T'O?@o{ NxN[C3gΠA@ɷзo_Dz|b<rKPxǏA~`0H$Rjj*GBuC|߿gΜvڭ[ >ǶЁVd666v\sxx86]|ĉoӳh7h"@2q߾}=/P uSNs1H>7+b(P(e , ܽ{wK);sssWW/^bO>,xP_~sΝgϞ555tttf%))sZ;#f3}777oϟ?'d {jb!uF6\ŊU??g>ЂcZXޓR Ip႙>|  ڝ2+ݻ!NT]CJv0o}.--MOO>:>-@偟R@b!uLu|5ޞ kkzdMsLL 6!CFZPVVaaapǏ?Ot% uuu[&6 -BQQQo{(msKWI&ck}k_P(G  #K⊊ AmCB0vv6 O^^T9s>Ձ6> G`'qd߿b5-,~nR3i- +**UVV!Rmw4˄?N`0@p UYCA? /qPLN,ԷO~n"XxPMLfוtbo8CTkL>pz88|7ؾmgĎ\Ca޶"zVg'Œٵ[ 1b4v8M|-՟w9 s~膕wHlGT@  @   @ @ $|@ # @ H@G $|$|@GG $|$|@G >@ @ $|@ # @   @ @ $|@ # @ H@G $|$|@GG $|$|+r9&@ =L=[@G \ @ @ $|@t[##ob{$|}@ Q}O?L @ !@ K2* endstream endobj 25 0 obj << /Type /XObject /Subtype /Image /Width 507 /Height 346 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 1791 /Filter /FlateDecode >> stream xAOagbX'1X#!$Pra7$'MҒJ=MPiSt7=,iCmO8隀ZsxFT+u=0JAg: 8cw)ҭW0EQ| q^ wW)rL!`*frqp$;$rT*b5uJs8 'ڷgϞSoe>ʶڿ>0ʿʅ8~ qe٧\<~,x9 ә8Z5K?L>O*yZ%gHl~ܨrw`84<6=f~67=6<GºYBmH_Yj4f*'ʆYJ}NL5V76WSI;}Woܺzz왔K/=#=#=#=#=#=#=#=#=#=#=#=#=K/K/HHHHHHHHHHHHH/K/=#=#=#=#=#=#=#=#=#=#=#=#=#K/HHHHHHHHHHHHH/K/#=#=#=#=#=#=#=#=#=#=#=#=#K/HHHHHHHHHHHHHK/#=#=#=#=#=#=#=#=#=#=#=#=#K/KHHHHHHHHHHHHK/#=#=#=#=#=#=#=#=#=#=#=#=#=K/KHHHHHHHHHHHHK/K/=#=#=#=#=#=#=#=#=#=aL5V76WSIӗ'+f+Q24K?h.TJщo_7_ߤĉCcskwa_z?Ozj5?xiݾ67=6}޷O{۽Zi{ܾfڹovsmq:Z30/__~wy#7onnn{ׯ] =tnx5͛uhzYY^reqq[\reyyWwjeia^'(l\(Vӵzr'F^t۵[Tvk; UMW+B S_yO(_LF&*SՎLMMMNV*.Ls &.\T&'[wi216Q6La& IrCIR.Jbq+[ST>sVߏ}*3\gѡ!zqw(̈́Ad L#x2AJulx.;6g- endstream endobj 21 0 obj << /Type /XObject /Subtype /Image /Width 400 /Height 412 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 26 0 R /Length 62568 /Filter /FlateDecode >> stream x\[<}^MiM4Lq'60{%X-KlӈAhBH ra8~^){Ͻs={!B"D!B"D0dGB "D J!Bt8p "DM?~_>Sկn_b#C7ͳ>K/zӊnnnx<>***--L&444ܽ{B466חfeeLJzyy988_t?|W!_~{\!z_׉'!% |➞H$uvvVSSrʗ_~yȑ?O۷dGP2?9X& D?/Pcbffvԩ_!:Qzz:1@t N"LNNRԊ ww'OB?P A*["""R项j3g^|_!:o{??r%DLbH$>shB "DTO?okk}x$CCC?/[HADž3<a}ঐ,?YVVQK/i޾}"DSVaa!Gri:::X /Uںѽߒ~јW_Ys^׿~gyLMMw/_Gږ?F!ڷOox$۾>}O#A֦Vm}d׎ջ^{o7ߖ|yrspzq޿޹wURRDHh^ϝd1+b!zꩧw@m#vmh 7JyO~o?Y^T^=Vhկ~+99NVD"3dmՄj#|[2pd-^^OECHŋ]]?;@֎۬6+os>|>wާg]KV9&ĪT#B#hH<꫻Xl |,:ػ겎 3|r7͛om!?ꫯ(ʓe}$ }ٝbmj+-ٽU[>ŎX!Dr@ ⋻ МnnwW< By9>>#TZZ.y˶uz}1FCr ?so}S}#+DWǎ]XX@2hvvVAAAIdTSSolsDvϫ۷oꖖ B$vQG5"Wk\YY)H,ڱ&&&233w޾wAlvJJeCC2yZ\\p8&&&X,vzz:77^oB266x@ LiGG <'BbRDW W`?P?y/DW q8T@"d2H+++uuu̺vAxս֘6sstW?5556W'BADOV᡻wooۭ.\n||effV=@!ʚx<^II *iaa!Nqe S(OOOyyy[[\>+D~^ Pszzz 22 DWTT*++`5SBADO,֊FA}Z,cWII ͆9.8a`T*|Rzzzmmm+Dd.mffhll zxx7'X,0*-- 褠 k޳WHGjm.q\ QRR222rwwͭ <@h)d\ZʌQ-梢d u\\\ e2.g[y+D)QQQ>>>VVV,??JNMMrL&tE·ZlKE"V"!!NL egg>"DW F 8044c#''w5UUUSSSǰX,x+Ӂ' d ):::00 hmm}ƍ֭[`OOOHRCCC=vWHGj7hepzQFFH Ghoo!%ܚ`F.55`nj~ח?.s+^ Bx"D^! "D@^!7+D!Bxq?yBa>) ^=`>ʕ+7oޔ۩`_%%%gg粲zDY&&&Pv!]iE>-X^t5 "]v`2|>сUbb.K,}_9B!\a.{(í822Č.ck3<F޷aKRq]pVQ2PN8zZ^C 3FF4j~~~hh W  Tx'.[WW׷w/Ͽ{.;$nӀJyyVܦRiQQت:p y9>G'B錢W䴁_;/r.62'&Bn'?\?bbZ$#NKE杯o[8w?=yj|19V͑+uM_|z9Ѳy|䅧:G:%T UGp`tfW?VEۡac,Qp_===***WPIZl:Tnik^;g'R}q e}c^|ǷRrԾ>n;ot8~e( F*9tl}^g/νfsgX8{cEN7>M˹z I=Q.( Kw̍.WD O&l6#utxBIr{{{%啈7h gg}S;li04m$1mpsغK4u;Uxn}w^{+?{{OEL(Ǵ7߸͵o~(/_4F2TrGghE.F{rXCM{ˣN<|p\zW3Ɂ\+pMM x<([UUbֆPz!T,,,Dx*--mmc!؉FO럟~&nxcES55֖zkĎl\|~InZc﹇pdKq9LQIƵ/ۄܵ~fdzoxw-8"?|ȑĜL |k]vdN ^*yTRٿ(K@^p8<(A xA)uiMW_jOEx{^7/uG=~H"ySw=>26$Ni:o&o/8 W.~syMA2GSz㥗na9w?:r˷^;_i`uSo~rphbjG>*)WtO믎xC`zӧNKv1w --c|PLQZ(M1FWOo_WGvfFzf69usM"54P9:<-.IIYkg8 uem7#-%#1ƦQI8gqqiǼ+L;|}Kkmu91.]}͵U eUMY윂‚xb25YTQzEW? ` FZc\05m8RvnEumggwggGWOXuUee%ԊT+Zǫ)VN^ }T[^MzcP>7g;¦¢ڡSSRl2Hٱ1q9Y:*!16.@wWw*up+2:ck7mlvbExxDVym.V)=Z=c6Vظlk#O k;BTmo@b\_>Q{Oj"98cSFuTUp!G54ooW&#rrڭJqrcW{{hhHA~׮sn^m7ѼGpt֕UTPV]x^푹zx>hٵnr23SZZb ۗ4`Bdd304RQVRQ4 &0'xs135p 3ّY̆)ntuUe mldt|`#6<<\|IGGOOGtuq(olG[₣9XYkVML/=uǡlW6޾pO߯/_RЂ7FSr4ԔT mu%vvaA n%g;UwaOw Z嶢mfNssC'NNNN:WP31Ξ&wt+c:W{#YS!TCkPΡBQrp;8\2{E"`6`k׮=ijhhRǢ 㣣nXww䒢'04n#r"d@ X cKҌ^EDF.A;9cҒrr d답qw7+.)0 Nle+|aWG23N'Lt}Cm#ístհ>zYIQiYk™Rr~FV^J\ 2H4gb t(|sI\_mlh鄲tuYXZG$Xffgٚatt"Rr2S#c³X[W?7`vpP/(,v"1-:Z$R!*|bxLuSYǢfƐv䲻I_}aIyZщ%$܅d{ Lq 3gllNCl:555** VKKKUUUIIACCCWW-88O >Ms'66{:rS=bS;[CdC.?~|EeyJFvNnnj21:!953;&*PMfseFW8ݺ};?l+8*6ꈶ0 r8*(*D[52rIlkK3#vXT00W uN$<,-,UU\1n^_ dzhl0z2͕r_o^U󵰰5,(.DY[fKgwf&&QqqFz:v6(+Gzh}}`BLn_KEqTtJ |iz(:5evv4Ԕdegg8RSSCP>>>)))P(===CCC4b𡷷'&&D"m5e>RފWs(۲NMoV^K =-QuM_B64'ZPAҌ̌J0],r';::i4`U^MDbyIf2 & H)9B b%%Y|*tg #Dmq{),-ɷwtդcc. ܂t~{;Y_UE$ee%F[И鼐?:0HK$BtW(|Ο]d2:mŞD 3zX˓Mr"P0'e7m]pye``hه C ×RPMdr\\tk-W5=~=N^`u ?[o_Zd1h}Vpemuxg [sK݌OqXPj'7ǑmU˂) c?a ^.̼kjjB{ jyrͯHen[{rPq`G)XF`\\\ pwpp.::\k.xOn^GG@AAPhI [$"8;^MMD8*** \,k ֫@ۊZW_KJJdMLnnnVVVAT~~>qyB`#C+Ywwwo3${)@Ex?ܖa p.cccSSS,--m͊P(| d&"(C^[B윙 Qv\ @E[ΏW"yE+dGyLOC$#`?YO`q2 ~8===M'޾w @l ,=ЋM@*++㯌#Agrxdjg{OdA<BCCC)]  0ɄQLLOttOrwx suuӫ;p":мH$ v-deea;///_<d.Vp2{>$kcc#kأC<6aH9 N?p#:fvv3Z6Cܷ1_\\R?n qk~A+JKK{tΩ5sj^MOO 7vTZZH3iOJx ummdvg|A(`s%[aL:SOj꯭KcnYO9448::*}$ x \.`L% 痞p^g qm&% ۯ d@^)p;!p/@WY+UCFe/tPx V`?˷to6=85R GXrL)lo  5tD-0Rʱ:^ƻY=y"&Zi咎]PӊKǡsS?Cc';Hm-<ἂ#9W]ˀdr۟OQMā #?ҹ;A7v)k:(;ƺs(;TG;Gd'V.inJM,egX+ 5tC7>^jhUq30:{̩JQ|b_UN3&5kqyH{G ]+( Cf^^UUU==]=T- qg[ ÑaI&%]]܇~'Z1ƤnJ*ML., ٜ=;d34q vSAgdfX`WW$>Μ2GG3Mr555p6o>B99Օ[TC"KEbѺybE\uD"Y~DL-N7jHaiy5%U7 %S:Ph$bܜ`ayyP)W+ 4-v GZZ~%1'BrռU`+Y, u=ydlB!p_$%eQ7jOk;B#fsK p+羜b1m~a^*d'6/ ֏p?t,tx9-M}lZUV!xFy V(ל(:+:^&Hx%5rwK !RԤdG{[ >&\P*,' JoqR ;z{8#XGKK$o:,P,Za^ c'ySPnaPWXPO3F+..^ǫY7VUC'<*Oᣓ2)ёaĔj%ȹА [?np~jjfQUZMLsqqMȩohj4Rё1TElVzJ?ǓjYcZϋϛ,ds̑Ʀ 5UQ!WW脌@oWGm?)>B^k`<>ew6BB"bF-QQaT}F^gş:u.'+=+(L : @*);9A &DF'tSb}}[:ߟYk?hjI=`j `8>PWY[XWAOLM- 75455D˕t:}|Emmm wOs|j:~AX_ _++ KRPgFZ eno:w,?Wj3LL(~yy77oNrz[!!!,O \6!6e] YblfneiIWpW?$ή)=>20婭vp"Dź9a3S"҂|h!;TY[45tr` CNVv6EdǼ*Έ1qj'Ww\0_l|=4l\8gdg`o0ðdVe«; al>sWvJ8GGKemm /*/u)**"˂[*'':;;.-SSqAaa~X7o?s k}}= kd~;Ơ-ͭmvTo=Z(Q-*>!emp\@pqRG|}<}pׯɹhZ]E_Q[O퐘 NUPV3l{xpA*QVies£lQ1v}}Ho ׭lT4{@ZC62lhn 62W0ht;ꪚ[Y;`(]^_YsKQ>( ]]߮-4ܥ|rݶ5DRFTR|fP/EAEodlQGH^=WMMMFix?_c#SOtd. 0ps4SYuUnnn?q=L@B QqF:6XW"3-~n6fN48!7:l>%ٔxy{)\k  ts<0rr]Xw#>4zRCYc|(IɤD #5M*?/wW+W ,ߔWS򍍍.6_x_/~G-H׿y/G><7899H$q4mb($A`u(ba[q1GMNN6%:9{)r J S6fO.^mtuuKJJ+ gvEc J:)!1sz幬 x%~ཥ\rq $G.HKŸ8yx!GGǘĘ@Ws J@)'ޞEU L5FDFD765`6֤%{zz;ɥ4s|v]E;12&15Lc>}qq;::>~?p>PtXlTTp0O1LgG/OOOBH135ON E#Z;;kҲMhjcSBѽ+KKK77âk׮'WP1e헖gXѼt^""5)1[l '!Ұu%(ܩ)Hrwo:<\]QM"eU  EbBiMϮUʫŅ 8_ XZ\ a#4&kT C`nvgN(/ceee Ӻ:GL:mp&7+` =~_=g^{}ի 755,]MMh}=[nݸ!>ϙs]xҧ|o@!Hm7(O=zZZ>>Qtpp;99<<8B/,,N"2|\k 踌{֟q_uttdUUU{ylkpfDPֆ;3uw.cqMggvѿ]83\Ss ;YWgTTT S@_~xaqOwsS3'S[^9>-`06%&&YYYΞ=_ Ƅ"_hiiyyyeee5ckXeY1j<{KKKnn6"*+k;|OUU ҥK...EEE}XΙjNkkk\?s?*v MMMS`;_m\ DOd vm*`u,KDZT^^?q5+y h$HEk_o?y!ŋ---CCC7;}tCCæ:ƫ'Aau* !ڍIG .닂 ՁJ9OWVV"'S?WlWѻ 癄[^Hmby5lDslδtIbKSkKKki #Od#d5R~H[]^ellgjiiillWssW/‡6{U__Wv{k>d4}>=j^-Jၾ9;T3nKa*DG ]<(F9diQ:ǟ0\i8u^HrE=y3 a vvroW&Ψ%D%44G;f MzӢ%WiYnֵ3ɬ&"[ uUTT|ܹk?sLwwKJJ@ԃ,P+\k (HmzEDDPMy$+Ht8Vt4kJr<}l?xk#i*ʌpwp80ַg4WGHuwu#gWV/\ץc} \mh3Oma5>Aii"«7PVl6{]: F{E('_SS5]gls{(.\XpUU_ łkd B(X՞x? bs='//v/4*lAL3VJO 73 q:rBRKrzŋƶ%m5E8;S3RsTbճ6٥.~y= 'p|yKҮ5'fZ6at|o3j7]1 u]@7TWmmmo:w-$q9Zge`` Zob񬗚 i5$QxennQ [nūH+k׻}tl^oy}gNjs7vs Vk iuҕgNvPR\QCrYgKIy5St+Usvv49tY+Qh&OUN3:5J8flp^/_xD<omٸMC~ԼA8ׯ^6}^Ceq׿UQQ@ `[3?©6l1hɣ\}9JN=|"qZ^ɱJwJvܿ /Ew"53B'DFi^<"MW3mjo5mm_dޥ $f|s)fկv]+&=:t昀0 MxW'W.dlG}uu5\ǫ}5?lϫC |xC&fn>>h+u@R~a)sfJz:m tqu+*ʶ-o鼿$MOysE1>ڦXcoh)KկgϜ!5ĒDryA[&YExzJ_Hcfzxf\}}6'W¾OebV[ji^V ɫFY5aA1a!QCLvw=9&<87$ɠ8`՟depK$$%EƑjrFhp0!!=:&W,]N`~v$9'dvS)̞&OȯKO|{l~Qk( 1Yvx5J緶QZ:M{KwccU{{`^-KZ[)>~5/=:WKmC#Lta?j^"־%ELjzxr "P Lt|)JϞ(ċ+n,] @$/~cRr83?{../}ߓE˧u)ϼzEe466644Fq&y,HGGՐ + vHּZ,)vsu'E@ j^KE|r^un#KH&LN+(!hVOO??9̉'dD]ܪ; `ZxhAls[sPWg9F^Mz1=52/ne17f!ԓӿ]>N]]@J'&$9\uN \X8/ vY' A+*Ed{͍nN~!C#kx5G%;^u`T)ݭI.nmCSavO`n^LzqQApOkëoKp'X-,K<w7;;":kNOrssY @WYeueI uO-Lr;Z[iC}~({ƖvDLRP(\5\h2Z]1NXaҎyH8vXnI1:,5(2'_T6,B&'2>݃dYuS3`#BxyܼﯭKi.+rǺS3몪BL.{vv6???<<?Z/^<{ǺiiAaaa~X7o?S3Km-Cc3mMU{g!ķll1ޔ.'BB:W---lΆO,(*MNps~_S#~>rP(;C4jR%$M9lN^j&m.,H|]#cm,RIi~>.]tP[K޾DboF[Oj"9ʮ5.gmLi/$%ڣ1zT]ܼ(]y5^oP]ySs˸$RYI@h'''+)vvwj'fF8xVK˫(kPGzW(*11qttt*[vdUSSӲ E{xxBC=ݖ-Phx!<mkacܺ .rSSS@m\>BݼqF.>x|J:͝YW~n6f&z!4&;"/:l~5K04CY[xV- ,xurܼJes{=ۙHt@YX9WTYӲfv~UbjGCmo̫h[Kpw5 O! Fc}8Ҳ"5Ԕ%9([0MPb nxn255HML;``VJ1vf9TprrmbL.[q2[+Oiwx^yyyY[[999ll=(۬䄤D[÷\qrteqk1 Bmm-㾉Z\{zҴtW!aa~.^vhg;G1ǝ?suwsUU Yy5=R}ⳏC*;zi}yt$AKK̩n[K@B^%F`C~zf 7򢛻wiE=ss ]3tks^`PƦƄx;Xok;jkc<|=0/6051}-otH) qr8OzQǎқLZ4&u0pp?xUJJJRIIɓ0 vuu5yQ(7Lwt?Z8iNb;Η8~~>ƽDEj(vI{w$hDH}r;swvީg:_L&WUU!~pE]]\S]Y\UYSQho@-.JKrtri`syC_LJLLIN,ᇟ$ %Cmo:wP% {< 2r"LH#Lt{e?;-zxxDD&tf;:;8T5vc\,-ZɈ26W*))jܿ469U]Zhgee-Ϸo؏?cQ=+\᪃cx/gyY#9!5)-9fdfGJMT[* [rʭ[{zp*ҥKW=XACk5@ʪ,DO(:X(hoϔY9Ae|- M `Lb1iS3"a2+}W!_q ;Bq :./At:`N'D{{Ğ"U| f@(* \%Qh|W| &o5@+s & ^X@m{Ws*Rp|. !tPy\]YYb2<ݐp$\,h\bpp.'(4/P_ $** sbb@ <8Mm_٦ p%dQɢMGvKKL0vJ\/FPaUvx0YKez KJɋԃ&sd9̼VVVMMMLNN>ZePPPaVȺO^V$?Z===UcLY.ryPsP񪦦Zs$&=jm+hl; ZZZW`` ?{Q͛~?S @.OOOtχW4xU[[W L&Z<<<WW^^^'NK## YWY{]<2\TTmojj %%%A_XW«R֤wk QUU="lv__`Y֛qCCTyXxظKnLLLQ SSCȫ k,#88 m331(HLMMbR r߁coo)B}}}W4n>狂:99?V&xlOZZZm&Z)W TBm~ے_xpPR6Y1YxJ/gg眜%«um[nii92k׾曕WBڟ2 gn-`pEAǙxh&{0~L)ʂn 2+>(++ǙJJfWs+]]ݮ#3~u5-lNV#rii 4t&dq=pvKGAא,"sϕo>f*KfffV{[pSiWQ^vmMwFbX!зdZ6;cBq,51y晦5J/]yC[;n{iqgDpOv6;1J"_N&* !cHǏ܇3̙3W^Vկ$B?̲)~PjZX4|gm(Xs{|ʸx+@l/VsUp|Oelha4,1}79^^ F E=W^;1)a]]> zvnUCNI2/ 0P׭'E,OOf^=t}B4B#Zz`}ݻm,'?77T@R-*55zT(++[;mxzz^xꩧsx5X`jd[94OlqjU6U2ww*4qEpՋrQsKM"Z6<{8llT?}CEpVcĚW@/ .M:B)]UN+)8տM/6·v}'Wmmmn+`jjucq-YhǏkH~E... &J eYx<^UUҒ FLx!/:L{lj+Kczn>Z8{NpI[`_wmC;ʕ߮,wQVm s1DC3`ͳL(4qv2rwv4zM=6&5/ˏ>7v†ꨨV y%@ x`DuvvP$wPYY988k422 $m,];ͪv>!Z; <>]`xO51 gll}rtdwP8%(XnY[10EKiFf.Q/F]dpG{Qm|MA'*?;OWܺ1OUV5OVg삋 4r|0544t3l7xc-fggI`GvԺuWd; JZYYm8,TpOD Ǔ$LJġFa,l#sFG{3#*ң0NN69qN6&ቕ#=Qo+M!+=]zW;'o{Psh^mfoMVu'/,2󑸸B_U[X aql #+%%% g:ڡ"%< ###7c4LLL MˡQ *W ZY\ѲxJ%pyS)8ckFVDB!Si ._0h d*f0p${Wl^$i%~wyBx^i O3$ 1`K;I1==]WWz\dP^=2^/ C&#+G.vx߿kSSScccIwttvyxԂSvk^QHM u5##c3M'L1<~y%"M@ǫљPtx†!G!wss;}tBB¦:$ry.~{KK YX3)sc`osmߚW+e96AY)9=A#3)opD}71:4!a}mx0<|awF^%}sxE\HO`4u8Tu||= 69M9M 53v67Ťnpr~Fi-+Ÿe5錶q~c捛Y;DDյtVVyz9;;gVMWҒy2mlnk89L$eT-RiC}=]~#kSVf1scm6N3scݎv6ٕ} 1}X\ӐSPQOo*L Oz|{XT`sm]QgZeWzzzGWݰ!///+W0I -5w"s3woںښ&W_P,WK4ssr0227DӾ]/Db,bCUM >SlH ~>CWwwWcI[Gw/NOKGIQ5"69"8}rfQQR |N3;)/=#-3|&h]^ӔshzyhBbl"clLG"pa .Vښf R+Ν!QJ=u0IEt&{ϼ*ˎĉP[}5|ܜD"=!Bk x57=nao:x uἭwzcq%eqw@__T(,,b[oeccS" `} m1ږ#9 |\,, l]}IwX6OHH012-)-)t_@Sx0}u=z_/ϫ=, kYN~09իn.,WQEWVD.1&y%1 Wb-,ãļM]wpv^Gߠ^+uNTS{ߨw`8͋щcxPg^ +N[Ye}]M ==3q>_?YPt?=mSPSP.`*Lzȋjٗ˗/W^8b$hDpgXH`xxC{x 嚚8 寮:55U^^^\\\PPq=tp*|utTL]0.y z|b^Zk9zM,ĄDfo=-!<@[CUТP_뾙7CcWab^y$w咹!&9h,1-)Vuuc&=-{WyIFw5kj- MTnyyYXjlx$%.ʥo@w &=h{mF&[* 1E~9;Zt'E9g\wyĸH+ל1._-,76PmK+;28=IN&+++l JKr3sHQWV24mnUQA~X%EE%Uܒ*&)|}:( XUM-Mo@P{{WNquKAV=5N6'VFzL "mn޸q7.zl|k, aA9T*=,WIIYKK Y\_Snsr%QuK. d&ũ)+huĨ*)(7q=j|޺)o=::`l` }_7d.Ņwu7Ԧf+\pܕr. c+ep`87U[;P\EEE[[!, Wo,nl?:4BIP.%/|+ Z8l6P(~A͎Ec0}\ܼp0e /|/F6@y/-  pPSh.ƺb\@+Ү[^YS &Tp3veoUTmK@?Tj. * px\24cB3%cD2WQZZZ@^AGK uXa-- ɻlRQQQgg<wBR‡Mvp2RdӸҞ$I$W_X 5$fC322w3~%Q^=Fqss}y} 6588u'>Ǒt;9xW|ԩ;(Kz_x뿐(kGLPPJ؉O?500Ϭ<$WG׽x`k#88XMM NMM] TTWFIlEWF7dkPPP`ttt444Է===, DZ>( nnndgg}7`zV"=h/my4Ayu;ʫ$`7B7_z)ZA[[͛SWOlS,))=<^yWG@(Ƨ3^Ƭ:$Zwӡuuu#pN<3pܹsɤwQzꡓi177w#=O~;vvvmmm@asGEE8q' {ѱ9 (Nun֥Ksmll\ZZzPBV....\݄D]]]w3:o АF;~׿޻wH>88(,Ӣ Nǡ^'V ]qTPٿ< 4<~Gnb;/>t!>>`P qֺ *Ϋ᧟~gyx `£2G"`n'"rrr|_Wva Y>1W_3==@ yU0}}wy /Lޭ5ua i-Bn~ر7nhjjMNNp8CCCEEsx饗}gèCK*ZRxKv ?+~!@͛mvtt%633Ffs\P[ĩf2 evvvjjjbb&..UMMԩS| xy@/D]{脄#+ٕPk˒{}$[4x 4Uy@0d^Z_~/˻{I0uttt,--X,0-222666>>>11155<͌ HNN=@hp|}}mmm^RWWpkDo~tD֟,(W̵Gi="4{ LǞ{_WWUgS p<`N(!4PRv4yXꪣ"Czi(PR=zb (P9ߓfYF)TPe .S( zS(QF '֦B+:ClA](І .TP-P?A'zx*GF+TdD{jtB0 P *iA .TP }q[Qڶ [F.ӰB }(.{.X#.tNc} vsъX*OP'Bv=AơkyQAQ9̰B'B*Zh ʮ: jqB .Wrغɺ{hB)u/Z+6PA`y 5PAq9PpI_Л0ж}ׯ_ҝUUճgϪJGlmm w~ߛJ%$$@ЀbX{2*aeeeʪ ҈Hbbbym Gt?j\M.]U'v/ rvv񠆝ł>IlllUUTKyyymmmiiiSSSAAAKKKNNNGG܈4))) r[챷iP:hw :@Iggg^^^ww7/;;@:44g@ŅJΠʊP(F拋===;?H$]se"9 0'SRW vw%AX,idN"09?O8Az kjj ꥥ%99ѩQp=J )ܹsRUQQ8 JQ^eW7|s2Jjd~G+&)m^ {(W)Jz#ӧO^RQ.+C^ml;lxf嵹٬=  E%>Xlׅ r*-a ʛ<yE_vWpM9yYJRoY_<{19.gJUXCx}S7'{FF^:u5ܱQ >5\mC+<1u` [ZZsn)j&TLO7 ^~(3$4Z5"BnUqǜS .$i6='u^/]6WSO0 =slaII\'D|?#5U߹3g{FWP^ PyXcF] NOZ;8 vIs)qV$ک}Qim :Pߟ|O\Xؚ4tdDa,tjT䬤Hx5??7n)}뺱YvHtg #;`Ϊ# =o/=#NgGKS+L32b]Zڴt\N449+,aDEQA[G/8,OЀ'I;W6{?}]| 5UoO7#+gf́_qAJ*c^n6ifnmMP,klgy%O+/4 E(\quWQ())uuv r'Nk޿_ꉱ/؅}`od[RQ^|[JH@ݼkWo߽*mb:3Oq1)(L 0WUN몯PUә$W6ڵU50%UC<]g8kCQza]\0iaNy9QVޞ̊;*+3#kLYI+kaWԴ̴Χ'';vaJGƯiiZXWU]ӄ0*w'q,oQ\͐g'|AerJˊ;icn"wneU j_:ԼZY[y6Tgg+KK{'Imղ 47261ԩkh6477v 6@6v 6$fd[^pAIKlFpvb;x8é`n߸,'UP-$lv]hWRV136.ɻ}S]#kΖF-u m-uW_ҷRQU1668ۘi5Uh_}Šyd2T@~qY_G_f)N&ܒ=<=ڣjnavE\POgk۷+޺oke`NΫ#FGN3,3VV$<|b KL̑Et"Z$/L 4L^3y<.q7NJXgHx% !2N&d7==ղHm_gfhT F]s<مƪΔّqPPSY+F0:&gxް'E*U(L㌥F"Nlt-Plg̤I3sT"ye ]jeY`0 X,3K"-)+Bk;<%.6| 3E*ʫǨώx***oliknniUTPG*59yVV%FjWn+675zzXYƤu;XYYY: z`mlZO«PWW\VrrzFqAVKPb|} 8ess Y5L9 <>3?/)55%adL舋OtrrL,\$wL.lPSLoDOYyqQxx@oUX\rFJ\ni-2WdWؠȄP Ƶ( uvM-96䆊N^Nڙ3g ՔSPT^bmF^UG_9w9a'3mSGCYm`;S$qٹ85=,-}4'7=픘`WWssl=}S= wʻ yM 3N;U,ݔW.>q6. #y/om#۲4 $-\RnYCoH[ܹشbM=$= }80<.CA}ÓQ^dWm Jgkt5߽2'v45w ;U{ssc^[K')mhdlf{ݸ@䈶B4IP=66Yk]16<-/];`nntO1 ^đNQWQQYglv*\Ҹe"3N[OOm5-w' Mw C:ZF6ÃFZ>!4VWSm>ɳEՍznzz5hWwW1&Py lQcs5U>_7gw/K_NKOIVyX?/(XW?啬XW𪧭AEӸ`*-6~tGU]owp|Vk6W~Ȱ@MH{gshA"~ƖLYpqHKͼr|Xd~Tj>ľʈ L vupOL57wxN,%5}~d=ɋ`tyc\]oU ĸb,=K(/;'Ϙ0OodܞJwo露(%E OG6 GIfYl`MRR+s6翾Vf^Qf&/J̞qRBj-qAABumQ wRr]+P^ɺx5 Y+Sc//}}oo y룣UV׶säefd@Hk隝h OHLHJ^b9WB_$'Ft  tvv)DB\oUC[ei~PhhppPQyVSfۅ|N~n~r:'5.Ť;m~?(,m9qnnnbL#|A#06?L 3TW &ˬ#gq88(/^c 3@ VVB!h(O1~fW :=WRqrFy`yL/&j*kG33 ۻWkyzEA -=}xtBSvld --wh///KKM-/JOMk,UUffVPK+$+`=59~v~4; ,cszz{&'jk LjiΝ#S5˫[;cHsSe[$Ngeϓ[j3I<~Zjw66s#9USS@N^~3 #=mw녫 8+GNVqurLDTt57C e]Cdw/#bbM5m1k? bMXlc[yd'5~%0PګFuחDM;!Zjq jyڞ_W}7凞竪6֔{b/+dY;`,mǻk 4u5l2RN}syAyuBy+^w4*7)*(H tUS*+oڹ}^WkTS]cmfebfcbdnbgvGA=,9NҎ2;mlrW`nT5M q!ϟM)Ra>:6)\5KW` wCyrrX` [N^vJiw5U|.&$">=B(EQn^swzSg`MNggv[_@C۸$+쩳ᑑv6M=ǎ}m效67кuS!$68;IIMy)1V66e mUٚ:ʊԴ==\,( .+ԸzbɎVNN&QE3}nj2", qNsq8cuqšvw랓q򳯝55k*>m 0  602/.Bc~;>,0Vq `1i:>ڪ]=|-[Lt0.W^)rVH:~nOo[s3.Hi5}Ww_1##&IIVFAF]{G<ޞ_OBm/^ ur5okecgQl d4UR\;9M"L@ {*ڤ}a($BFVO  􏎎ji$NGFW2hO[ :~`%sڏބ46dm]z}ۻ{U|QlS}iiʫC>lj~HbI߾"*Zޯ(PX ^I5^+)kll+'{æ+̙3ccc(p+gnnnee%P+ٹ2H)$4. Rke]:$69q!q nP(Dbm 5@D"WU/8h gdd xP_:j?ϛ$3 9*@v$+n&CCC\m[AvJҭ=%%Ԙ\(ItPERI<@0DAצH8,$uNDbvuuZXXBc\>]:/<<\Bi_ZZں㛚R^̘Co<"m v677CZ[[ ~kfH|Me$W- P@ BxA D)D8kQUH/~{?dJ`uƯ)!!aFd2 Ho OW @~)U&0J&9~y^Tkjj~p҂es|^nڭ >R0G#t:̪'WeVN^Q^1^:u l]˫~' E"._m+Prs2KWb869ӧwdVȫM3|W+c%6d, \^1)UuߊWH p] -xlfIE'+D ^TV &[[^-.O arr?E,q1`-x\I~e383071IϒfG"GFGFؔѾAqܼ5\«6]sJ L-1cc}T:s~[=Q+PH_2%L{qdw޻[뼹ykf=3e[Y%[b93 9 I0'`#I "RomôDQIؒn ]쪮޽yxsBeЪ\^b,Ē\9<0lm--.qRh5.jrmO_PP9m e$ClMzQR+c?`O$l///3= I !ޞqɍRq|L[pQ\M˫ 19)~}FS8t5Z5jYjz*55.7":*df? X}\b=ybbj(7gOMˊzҪ]jzHMNK#xG}4>7YU.9=(LNOm&TFv5fg%cE"?|tlLvVwVj7W&etu:W_E%$/.zz676p8HE(ӟJY:x4ηoQo]6HZ/((w 0LlsvHzԾH0Q\gN'65I2guzux0XO`mPƆ~Lqh|;E#O&LJ B{/M /-{[[::ISs*f29MzktT*uW haѨGZ վΎ5:2Dx%Znij_[P\vsccskL""vwf) F Q7imu㓳<WZrv 1٤ёz|_ZIwO{JQvjھ<37>=/ :[ZzC|lx?02,=Qo+3i3%[o[/N,ĩf|~U}oyzQ;NrrN\pxxi羾B"M&&H;\ѫo|HĸWSXl=!Խm`z (>SWٙ~?m ;|?W;U>! &yOW;3ꓓƃ-Vc:s###+pLȟGo?nYSy*u_3_>QY4O;|K*+//WVV٨[\\4sy BYD"MA&Dbh4J ,kmm ,lCO +++*o}}pcc Dž&b0P7-f8~P3/k}ߝO>uYW;b :)))!H~(|pp@7,++XLH,7?tN8b~,T ,hBИgn1` A~P6BM :@UNPȶ)fC grxsw^ȢՍ7*t˨"Ja(qC߁ <3T*pɐ4#OL##!W`J% I$T !w̷9X,K+w^KBpϩN4^:~| .'.P@k"*+̷"P^zsʫSf?ܷz'y^\?n)a-}G)<R^{Od1Ox㟺_[ϴ%',=Nd*Z}SםWbnOLNg-;yU+uKF~`D%=aץ ?~h17xؘī£?sK?cݍ[G.ʗ_ϫ3 Q\|5+LHY`*lmm+dEWH_Sɒ&DBc"B[bHYX`lu:PѨtFUk2tA,ȕyNSTJӨ eFofmnŒWHQH(ʀ"!_TÄ bDtoOtZ{6e"#2wg{nnN$I%u*ucc㿓WR!/4R Sl+.O(Eb1 4/JF#D9T6EoYM7WUkzlxߡ)EQ^}:wbN T&$ GFWUEG̯lOFs)lL^AoGJ-/b ԰q\seyJRjllLms:L^w#L'19Ύ}sx}IerAVONNbr2Kk{|};:Kt1YV^A[[HO-7*?;>;395;3и4;]\TVTI;Cm;x  ry9y=m\mT^ u#aݏ#Ϫ ˚WPƐmx^?>djYNkcޡ0Usc}}cK1;F^fJ!X?{Voq~wÓiI^V[Z‹bS1!9ERWmx}V7wa]So[mɝVaΕeJjlM̭Ou߇g7i _~ B;3{x B_s$'߾C~kzr~wOtx!!%=G~Ė[d04Nz5w.)9 TRoɇ/-WѨ/\|meBי+p}}zFfY^n6sQ%Eݬ(sfsEaݣ= % gY icH>!^wr󛯿M{(1&66>&2+F]rnTi?_WѾШlƵ;+CRz'K_ue3s'6c-.&|8ؗɠgGǧ's޾Jk;2o{V[wsruwpMŪ/.~;2$L+: Vk/q0_d?r~dOī^[xuKO?3glxQ:SSSo Auvrr¢vwޞUWŠ̌Tݪ:g5[Zzf_ / $u7f::Fdrx7+ڜwY!+P[{ۛUx:x+Bq}EBsEI—B qAɘvi^XLa:m*%>>>#=ޝ'><#7-^Ebk <+grxU?/} |_^&a`Nī7ۑ !㏆;?} 9!!!B:5윂Jww)l nskpphuDhj먩*+VW+K{0⒒dBc;e_h2vV秆I!NNA)I=cļ.W_Օt`Rc0;ə"bcq&=3895Hlh_YIMM(\_b1܆ #}ˋE &'?/Ԡܜ~A\.HKJg+p5}EO}ٵ<>$<ϋکbXr?rj}~}0aRяL ~^Ftn(PYu;j4*ζD&i5rNb",A!IR[=!W*Co ;,X,2>B >tFR2cZ錭|9 z5@]hB.TUvxuI^x`epy|X(HrHy{|Q|s ..}^"}-h2r_&r9h }^0d_e*dRȿPQaNwz=h`gHL8$ MK忇W,ūWzI.:x>T*-Q8x;f F&O, OWrx}Pƫ+W@gXl 捍 Ȭ .@Nb'|P(mX V"x | F)@:]HyO/ "&8lth H4 #>Ev BkE,yܾz@J_.Eu6^}塗X@sss0 hAD"]&''biG:zDh@byA:dDLLL@~D"^`w2 P,H|?H_ZZl䗂t -G`XWWXpkkk"PX n*488ة)KpdhFbӟОL^]|y~~}Z@555/B{վ>!'8A<'6<"`\,d ~@' ̈LCN Z?I@r"veeFbB U"Q{fDFA聻DTfڵk[n ~BWo$XtjhhH|ߵBr& ?CBTfテ_FM,AX4JktT1OWEoi!+W' F Շ+\;2@.?88h+R6yJ[[y^KEBc L*/fL %xTA.Qȉ(rq^ZV7Y@ 5Zz_\W[J y6ZBB^ˢh/x%kh!JG=4wiFF)6zF"93'GV#oAieCH7uph덇htG=Vx|NjJ H{1\Eybyux`%64Ѩ&wfrq^7 [&G~gtbS^^VovwvQIiMu W@ܚ˛E8k~nTj[[}V*,,Y\#Fkjj099*8}24B Uzs}]qqIwP4yj B~KppbGQw67Щ˵<\[w^o8;rWfc]bg.5:+F5?7% uImom%.S+kpl-$R6yd|J~+T+1o'EEFKX=N $#/x%c.ϟOuyyDUԋd_tS>`#Щ28&GM7 EU%U <hϾuƮat['O|iw`DdOAu!PvbnzbFV[t.ՖZ^..|;.%//Wuw4?qd|yFsMovfJRnYOcՋ)iaa.Os@jrJRVzՃ' ߼+d<}ζg@h^nFpXg8<Ĕt|3\C(P}zRk'@_d}L*ι ]ߎŲ4r% x zE㋂kU{{ȫ?(PɫW,#D׷h)ʭ-y3sTJ$uvv փ<@oΘ' <xW .@8"n!Fy;'EhM%P(]. ~=sss ZhMNNoeii CT8D* Αl6HŠV_^^3I#Nlmmm@|" C!DHW3d`2#d YwB #=~B* \!3>>3330ր9hnhtA5>fJ;55-x&555YYY4(x_"!|w, x<\r[-3\HP_39`2*|'~j ه8<.\7oҷ*χC 60k|PhH Xiʏ AB?!]ip"}pHd-¡((ҡ D`B,xpjbI'e}~:|ݖ C,Q^~(A`&EptQ1ٰh4ETBBBP^}qㆥR,J`#HU>Q^}? Յʫ7y*3?kՁN3CW;v):0hF[=6^-pyɏWr o+SwWz:-9kxZ)oҨ\P.Ƅ/Pjjsc}g88P(<"FP5'ʠDRTP"RSe,J$2xOF-˵jum4@O}Qד NId!@X_cs<.mWqgA zTD%ۗD2yCf͓Bt:4 +T Iq²%BiaA~l7=9)=318|Lno!|V7a2BB\<\]j[j@?,PB'>`|L iSX>EMKKy41P5ҕ4EfGEňdʞښV\NfRRr@`ꊻ^bW~EV+ä-bcbbãGO ڤ 6*6$41c+4Dm8i m݆CW,+>Z]T<#yUT' 0W:ũ7 %-<5alEq7*띝}q씃 WjսK&@}Vt\BIE@hrJs% HގޑI?BlVnn64D&ԚHo򪊛`qPXrP?w'Wԥu6;/`D^y~Win}`h;<;6,F{MNIK]=vϞEMέLde$FFƽx(-#7?xnuſJjm%|""훻FIηck|Zd䙞ߟ])*sJ*M M Щƭ8'Q^(4֢RT\S^e0ͫWe~o`o?7=B%ISss }IeqoԼ=6zviaQR/ͬ ffxРbq:rtd?473;0GJ%ý}Bd2>4:I#{{{'˔~:788$6\_^UN] %L*w7'O|ه|pZVVT3q8,./ppdgtjɭ(+mh#y%d!+ToJ./ov=>2KDUE z=@ߗTj5tDNuZB0KESjF[eU$I^i/p ZZ* pZ>zVg"ȣ \t)Dg|>80d2T"Z]^-/-o0_&*Up Ƨw*5mA;<5 Uye0dG_+oO(%hIhc O(ɏJDBh?؎SQ=xg9:$l_f5׋?锨,^p>zQW3 ]~By5^YڞRDÃh5@Ssff~M iy4=~k57 -,,_(Jvvvfffkk+Hh 3&KTO  x|MMMIjkk<&[__[ZZ677Ձ?`'7=#^YY G)++"SSSd2,lWTTH$08pP} 466% 672@QP7,$©QUUP X( A  64 |;00;Aalц! }=ݻwJ(@̋ኋkUz W!!-$$4N_^^܄?i4R)E,--.]S Śʇ ]3CH! Teو6dOa_RTzpyllll 6C5YX 5YS:䁟/c?į 2|7+W/Lz*‡+ i4e%Z షg~g kɟ(O!?} VRQ3|ZY$;ёDfM2.f;fOxe sLc Syu0>л6 8ZWsS*ΠBeQ^4jPje2q>Yܠq<ûkI$ܬ?{wT^s}||:cb=V2/W9͔@_/殑东œ\T=^>8=c 1W>X^1GH$|uuuq/]vcUEVoZ]o"#3sp$3/)'wlGx:"jK>rqui&=#:*͛D~U-Ӈ#b#SGۂq}*s"peMҢOaJ"=m}U*uNlPAiكwp'es擝5De26<ظyÿ*W윽MOR#]] Gҽ}Ek ӣR%qY98g@7W@^Wj,21=:#@VB!j4h'VC36=*m}WSCmAq9drNVNWlR6.1_UgonYr t %E 2M^eE~Oݹ\09!Kl-֏kWpӗ3sK~>i96/gWyhJ ˯qyaA%-bGsjJV"yhm4LW__{O7h_MJKJlrtt}*}`mꬽuq,/-6,:!/891)xjJr"RjLrVFJ|lJ"b,Be9^qh/lJʂӂ#9,}aQYo(Y>X_Q^vt<"݂1 qqNAa1hpe|BLQ]gP$ #z|+?TVV={5UzoY%9&(׷{ion+8<\O{?WA1Oz:ݹWYU}ʪǷ ZER=HxؾM{;ZB=t^`2 +R=lӣB1e|/?u)Կ|Ar|;]4OOmB#±wgHy:F%fĄ$k:W>d^鵚LРZ./( Nk+*'I㥥X oxru~ _0@-+*,)[9[=J;nuqNFfyԷWD6o4ܐŖVU&'.YVJ),*(-!45w70z|AaI^A4__R^#sK+kX,+H`JJwxX|2Lim])+...)^`gwoV4^.N7߃WTVwṱF:`\xe~ piq! Q^@J:F4ơj4{{p^"v[mB/+>/<Α&>| :>??Cbǐއ '&&&''I-2b'dA{{{ͯOUUU[[2=OlLhdP(\.wiidWWW!}}} #Ml6a>!E" o{{{r24J"")&DB, p,P`&P7L&*Z?U?G7'2W.]+eAp?/\#6=ȉf[F@A0o *P(/`y<R+v xh'dFvhm8X@R 342K>%.F"w2pF,X|T;yKnvv6໋F-Tfիp!,!$X./P0؄^c~A[Y2 @8*Zm r|f8EσrGo@{38މ`bGo1;.HD>5"!9Vm^AyL^]v͢W5p,7^ۈ3f+:ñ\{G}}} }huEyL^ݸqâWoGyeDȫLv!*3ye /UFF+TLTxmoěx5۵F~Syu\GfՁmO M/WBE]3(P],T)KTq9L|~fA ūF8>1)JsSs2R2\&٦OMYlLbq*wC"[`2 NlO" m2X |yiqqii GZbF*Scs @8G"كLiU5Aujyb|naIn-,.Q 8ԑ~^tp++i2y+P*GbDJ"1:V%PLRBW )P^myUcCC=}l3rG;)TdWeIWnZUFy?~L w;x={a139ng9~r#Cb"{{b"399nv/ۺ W-z\h5?6WQK$ODxYw'gk4ZB gh5D\[?2/+ v%2ӵ74 bvn)#51:&7(!26ETe% O/ؼ/uZuYID|LTP`P9y&(2acu5X?߆CW~;^m.ٻLg: ll{&ɽTNU'-0GqqcĠt\il 3#+!݂ґ46L~azz6>"(YT+llo.>ќ]]2{Լ Qx}ܬliCV\DQeS[]EvN2clZU*5.>SPx,gVQ)џ#?KJJY\b8Gf>BqFcuvjO_1>\}_^-vڹQ'F2sqiO\z+ãѠ _LRG@xU1'BߊWs!g?@47C( __w<6^M wfOFEbJE)1 i +\u{`񪪨pw E_EܾG6 v]UyQŋ酼̸Ȥ=ˁW=XߓgϞyU9ۤJW /]*UrozfKuIU?{u`h)**t/jhs[с$皿:xX]DDou}ci1!~r#|/\G;Ȉdh_wtd^t5T$FϬ0G|}<^>kmv'$Ja׿uxq[?x8rj;]Sӱ>^YMMM?˿J,CƃU ֮ͫ">;Iin] n#?|o,uz41%޶kd!&-::1峨_\ cs.@Myݻ6vԹѮQH ^raND~Vn˱ᡁ)9%9IOml| UArߊW<1\^RK[]#Mh9rEY65^DښҲE:uk4^UUR[4<:Bc8$Z?111vLLv8Ҳir[YU%0kLuC}U5M P^7D쪬6ۋʊ 8Ӎq&FǧffgIeՔŊ |-0G$P/y2>H,--%4i鋓us[g?H PBC23C# MmS1q1掾 }^8zCi2 ^b_dR /8\ߗkZHGXjzcn2:4Ag7!jƇl zRQ=1V*JbcĪƃzQܼWGaBBT*(Rb\

&hAf>W0n9^>Qz'ο^GFfPt^으BBxlOP:X[Y8ljY3޶*?{aqh/^pgWc3##$xoyzaQ*NM ]mm1WEm퉡F.r{ lP N77PZZrrKSy38Xʩ%X4G뛘Ux2KLZkPek4!DP/:GHMh6C ȥ⦆qS!5:r9ybHILy93 +9_LcT6k?*R.G-W*x{"c%e+gpb!skߖWj0-(8S[WMNIzt~[/%W8''+2<(͛P][[QtwW{{Xۺ {^A)5^aC \\=\R|FIdLnγ;7}/#:O,І:q0iqqiv/ZmޡQj8='>"(*&ƞeG{{הx/_c0+U{MeԌWLlK;Ҳ'7l?˝81)8>зe6#-S11޶u-GaY9t)9-lgg2+)ç ɋ}.4.}_bkJ?|対ztVjN=O O$]ksnM@EOPp;wIxUrwx;93/'/#̊篤+Ug_IPqNVNt_k:{riIsKgi+ 2nSnܻF_nPEx\OªJB\EmSFb!%"^=1dׯ&Ǖdϩ2YI>#NĒܻWQMq[u`'0%9vzP9 \zI浤HF\I ' +>ٕt)ϟrޟB'{=N8t<:qA 5*fPN-ŠBqѡmgݻӏ/0h|h=n^1޼i'PbGG'EzS#ʪ]!P;2i$ZD b֟0'F{8ǜ`m!Z{Snx"F |:*~>95L!MOMfĜojG@,^$Kh4Y;j oPP@P=PU9Bڀ\1w}F)ü4jʂTv|+ȃݷ&)3hE)GZZ@ظ&t:HLNb`L/ҿݸ&}ƸSPXH@zfF{zmW^U*U׀F3jHjllDfWOW/NediaS2l[LW^}<+J귙[SSNf*qdd@*o|?Sl6CWҲy<<$~fj3 c流DP0ȑH$""lD4PTCf#(#Dly&r|`W :֘R /U(*j``";>>QVY7N#BWk׮46lظqmv;wnڴi`b͛7#ڳe˖[Xa׮]P޽{7 ZXX@^UDZ{nHٳg,C UXt)X Xܿ?ĹU& *Z3#G#ǎ#GuqqpD"===8@"ƛ^8֭Cz0QUVXsfc/o{srʙt0zq͚5KZtB cD B B @ M endstream endobj 26 0 obj << /Type /XObject /Subtype /Image /Width 400 /Height 412 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 10122 /Filter /FlateDecode >> stream x|SUovڤii  @YeKYFC(B/l8Aȋ XqTT VFG~s3 +I&>Chsӓ{8UQ& fH$RL\gT*_ĴD&S(J!%3FyGr̞AU$LER{hqKV;[wڳgm[~޲nJT*IR1ʱMRVI.^bۦzOx^.!?W_ըeRVX! B)[9ud];HY/drRz*GTʋ_#M}Y%JPtY3 B1nSwԦAqA2kqJ̫ൿ]>E}'.{!bgsbђf* mH /l@ޅCUJF,.J.o\֑9::Յ1?R>DM*R坴ٔƅhlIEn)qYt+nydkn͌, ;˒>cթuHj5<\ɸAٮn'd|-b)?6C54e+*Wir㭒Y+H[(ȏN|N`"ȲA"'.PH!HM͹l3FR6D4,cI;oݡtu*5\eSTL'9. "Ǚ-|k0k4Gn^NU,|H niOR-6^2pi+>/͙Xy:U. + HDVtkHg^l,1Z69ʰOIᘬ3gX)ȹe$Vm _:ay9{z u 7'-\}J͜Hw|l Kly*=Ɋ5&U q},R?}P!ww}RЎH|,v;6$w֮D᥍GXPU˸E|DK;W>ִ|"@-C[AzX&*>?|Xdt>q*j |dq |Ƈ>9}\"T_=|${͌DsC0>d7C0>:<~>U!DMC0>]`|#z*>)s"׉Vp!rlDtB>>D?p!fÇ`|ݣG"Ģj׳ D_>'4,<(ڐjB$4Fi2xh)k?C|oy8"G&rvkb1sSD=lsz:,>59\҅|=Y||!}곂UvYpjWC0>rH 18񱷠3GT>ѷ|F D C0>& h-q( ># >c>c#sxD7%!èa_ Wagpɥ|7ǧÇ]}L$z>c38=>CO D8C >^&q#|M@P9.DGC8>fh$|wE7> rMtH&:1,~>6%|-"?>U͇&H!TmnTU|ΰgcs}G:i?0},-nKQG߸S> b>RK!Wa8Q퉜>Wd6?DժӾI/W|$,D?U5g((::Z*H>b>D1hkF\/C}wuDcsV,\{z:o_B~_!Z$ ]B/\bѽ )Ӿ2˅_@@>.K] Ed컡r>LCq-G_!sUՇ>_p|Y˼R}! Qɢa?!(AD‡p|]&!#p7C8>N?rWOxh |4C8>$>;rC0>Çp|,&>{ |M> uUQ-TrU?7Lh>P|mU%- ;s_Mߛ ;0Rc3PK8_Qw~1},7QaC,T.!h9n@#OXe3Z]0c*ř|/gx2,j~ [՝m^%iA]BGc]/iF)O+{W)&;|X8Ӗuf|({C">G1H\#]>(|h"N:]1[dt |÷fÔsZ t>\#vܪyy >{i뷛Cf/'7"=8͠W.Wb|g E E>L]-2-tOFx*DQsJ\qO Hge.ֵIM >@<"$xԽJ/X4Wս->'4UWr%Qæy&GqD҄ŵJlm[h1c=02]iI8_8ҿd"/,EҠӗ%|uNZ5'uh϶>-߾~N\=w"^5,y3B_9[\iK|vucWcM)]/TСψԹ_;hjݿtL;(U^.rE:8wT+/ʕK/4 =K І?ƏkhqbbV6Kn'[w02u֒5;?9wBk3I~iݰܲUuH} -JL?L/;ӷ2*׎nx~K62!jH1k;??wnJwwY^\<{ʘ׏q\="c!>~ؘhgrzvYBaEG'56Fc7~Lظ7.2ȳ8DWv\BnO|[^^\6oƤQ;jP',M)sTޚb2Pϲe)n۷x]XJD_=2IN>s~ 8h6bќF5Y@_7rz#"D*)DV4'b+4:z?M5pK.nH4l\ڴ9\qǾ;֝fe݃o/ܼ~U梌S9oz*%(k'thq[տ#Y6pQ{?רe6m|Nxyk?ũ_ϜxrS˽ֵWw~˗.3c<ݻkMԩlHEʷ#}4?I6akrAȝ7L= V+ULILÄ6OuI3ǧϘ`I^zyxGe3>b~{{wmݸᕗV=҅ޞC!y:5~^n. h;YGU_4:Uo"foZu5JhݮS侃R71mʴ3f,Xhɲ{nzV,_s˖.^`^ƬӧN4a}޹}MԮŲLl*o{Xc ?>ԚL*;SQNL[m߹[rO6|q&M>uiSO:i1G K2o]:kݲi|\'jjC OW5v\W ^1{n3&ª\")*MQq '4k6}]'ݷA<ܣ[Njۺy&bj XMrQʤ?'ý ޤm!m6sSV)N5AaOԉ_?qf-ZjڴnݢyB& ׏UFz.R}dp-+"FƳF%f7"IX.KT=4,Jg6*`]^.*??Uz^^n*JL* Ljw/YE'LXΉ"+\\ZaVU%%LDSrM\ƟF%MkD"Kez O2U H w]yF(+?u'2 _xs}ʿFJK+~}mbFOfo#ƜTWӾߝg]+揼m(2UC7d,i2s]߮~jFQ%I2J.vtqذ MCO)bd ګ;^?^9>dYh~/FN7!u>O1elH[UoD hG\w[\h1Q)3;==t>ӴV&Zf%nBXҔsVljt9,˼s( jW$bSʨ:D\sZ0vv ^RԘzXYZ^蚱(p@iS2b.h<).d9#>5\N9V}MHpG*Gطkώ][vuoGgx4r{ֆZb?<:B>:9-_iRgo+=˹ޕ6Y YpQ^_b7CWNwxZ&}RFd>uc]("3K?ҊwB>G>{&s8T>ʃIN6{GyFJ6YBjy.Q&rI;Q1hY h%E?w^VfݙXb>,vV/o9H6ܪ裓 UuKg3p׺><*axp/%#L~kzNmȳ{Fg!?O03sw•/ya.;2V[1ř.]pyꗊ֢t}&+D+z]W*BaQo•a=v[V^U:Yxij;i>Π>..Esfi֔*#ɴ}m<Cj{Da#xUD?pa++Q_J >lOߝ?a;D,()>l烻Ld6mY8lQ2h |" hՂ[^G ffP)v+C8B33Ǭ[&<#CWp#^ç #|ʿ{"\xO0q"\_{+!nvD$wJpxE{dI?W04~E-X8ώ .\R1QVLmBt33 >mQAQ:ݬXuCAd5sn9#!jsqUXqGWVAؾ:lD_̷؜súOvJ~6j$zlia1/fA#9h6|؎D%AE4>lEw-9$Q#[r;ޏ&6b&{oAs>,$,hBt>lc{3ܒk+a {g[db ֟-=m%yq>xn>6>3.1$v`Pcw*,?Q{}LYrv<`Ű*ܶeݰLZkOK~w}+nLMNj^klfY/ͪS#v9GjKd ֝2VXM"{u:#[zso\a7A d-(q+A"GQPTn~n^~ &mŷJ0EhA3׿=?ƍ8X3C /Q(J;aot,L}|!;v|FLjcg{A߯hR3趗Z>R!|GÕjʫ>3>`Ԕk7'~8L_wxT^}4r;Ykv!'0e+'}Il5#b4i/s%&s&'t0{u7QiρJDc_N~?E(=梼7ᆘ|;9W$=`R*2%|lv(W΁ql{ -vA$/R>{<0>R1A S8LY"_W6y9qa_溛_z%>U uv9;zJCH>z>>>>>>>>Q)7"ɤr!(UR*]Is;fgGN1ixai`^5gS?tr٫g=^pzQ^cjU$m" ||||||||]}YabJWޅQVWH>Cl"qM͝k׮]v-.ie?q_q;^)t ǟ/;]:_E|U!sΊB+Q9rsק<é$eXOv= +͟gbEX0ƣ|aÒg Kڲa(i\@>sK`k~9榶L@;'fDd _b[o9bgA?>>>>>>> _qc|wp_sЮ[2vRՉ"Q!ue)Q^ C9G}οPc-gqQ f; :R QBⰣWι+W-n>]n~.[Nv^u~߁y{]}ROطkǎ]zvnys[6q[6m{{[7o}g-۷زsw>wv;pl}A}(#d~z` ? (5|= t/ᒺ4vvPjVPRZLphX%{89TVRqT*Hu9'NWznrFbD6,PzL:RHn64kb[vGT >mfZfL>5=}iL<%=}34-mEiܕ0B{XQF>왡))C 6dȐ >bQ-4jЁb"} z]R3Q'={t5KΝ;ҹs.I]ugO<%wOضE!w`,P oڼe6КU-5KHh lLBBf-ZlOvDl4>6*"Sԋ2Sg qqq1u֭)QQuXBGǰ$g omS R\]aԇ064z'e,R$71 B4___o`s||XRkvQ0LRBRݬ@VOhuT*B& Ob%Zry8 cb[I*<.ިyxŭbLn+=!K`38`Q endstream endobj 22 0 obj << /Font << /F26 5 0 R /F30 8 0 R >> /XObject << /Im4 20 0 R /Im5 21 0 R >> /ProcSet [ /PDF /Text /ImageC ] >> endobj 30 0 obj << /Length 2032 /Filter /FlateDecode >> stream xڭXK6-6`k)A4=4=p%b#(u~}EY9 3!93f7w,VN~Σ]6qtP?T/V:{ktOO"|_ONG 5zgZ!^5:vƛV&|\ Y:G[ge_y YȜ|uSEg4 #@2"ȱvt_`7<3}į̈!|X j!ǫmWI |\$j>l{kG" ?Hɜ/)8 UQ-\A?3Lg{pC&Y`7ͷh>ݩhH<}~`q%&Wo^4#\e_ۚKMkJ=Eeۮ|$`В%gnQgdYb/Pv<5xyfnƚ2Or@9 P$S&b=s[2ϻL!9S;󐥀]{ͱ/_]sOC$x<'!5?@UZ-Z| ܰCa^(WA.v34؂&]O-t7/VHxՠrK' rBLTND, u&-2cy-5qhtD*x* K \Vio"o:yrքsLPPRL:&NL l(+%s*#n`BBZ2)*Z߈M17`s+tYR!]h($c %dF{R :=v|1Gl>#dP@C+7x.fhjAetJQyTa벱NLbK>.sβX2jqSAǰv79eHϹ6b).2(Rub :#! A&PN@:CU,ki4$ίd\Nꦅ7h^p'ӚLS0VХQ wDCyQAVB( v( DoJ@C|2qlIG> endobj 27 0 obj << /Type /XObject /Subtype /Image /Width 515 /Height 404 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 31 0 R /Length 45995 /Filter /FlateDecode >> stream xy@Sg3L׽NkNcNg] DX\0q'qAF5nq(F0a p^ , "`HH}s>o0`X&X& en db5_1I&͞={7n ٻwoxxϞ=kv˗/ "11gRgΜ9zH$ڽ{-[֭[|rYft:\ּc d}քf h=ʐ!CX,DCT Ɛ[ZZZ]]m9?-- *Dpp0qrrgZxlm;|Ms&?xyyAUZNRRM$4&&&Zks?66v߾}s2d믿nnۙAB0@߾};D p̃Θ1/{w. /`vё_xr,һWUUUǏ_p6ɏJCꫯ/^| x{MZ… Ppۃ;? cPZ8$Ti2ȓgXA~-Ӑy?}||=G###.]z饗[>޽{1ݿ?..nƍÇ\m{?w}T*1dA/8e@ϡf6Q-URRroowP?Co ޺ukjj*#GAl6%IX"!!Ӷ% R?7xwܶ=Cݘ?~bb"+ ¶m۾~!)SDGGc/u !C1ܶ\J؁r<$ \ヒ82 K_~ LsO>O< ㏧NI֭//GV9}/0Ǻ˜^x'O46d^{ Yw)c޽xئp{=۶޼yA`'۠9K>G VαA]t髯zWm͉N_|;֖UXX"K@~;=c*//뭷mۚ_|VKQ&L@ {׮]ÞA͛jt =jf[ݱ[,7nnA5+ 7~{֭؇"O?EX,~/\R$Iu ؁h,L|-VJJJjusP';v`O: .]݂jmoB+P#C,GnA=R_~c/%mcס*;;35&Ꙁ`Μ9-G걷=Tuě,}v ޽X}?snn.v ܾe`ĉ'NAʕ+ E"|+P-I2AsP͊0ak@tR|i,Pد_Jł*JKKP 'Tadeew E-[uQ0 L A~W.@)J& sA`zU` o^Mt:6]VVf p$ѣϜ9e}֭cǎMII1OyuUgg琐$mJlY", }֔rʒO1W^t!|ϑ-ʕ+'Oi5 A'@DL&388q`#X`ccc[h/_88~8*#..kڴi l + 0\(5j֭:*pB'O>n7 tppX~=uBDXXرcO,%o!!!1BAB.HNyMMM{-fz:]]]a`Or>*$/_h"7on; bDD̙3FYf:((e˖u =f>^K,a|UVVFFFB1bD@@|E fn:nܸ'fdd`Zjkk]~z&f!!= =LHHXn$>>>`Zш΢yС9(+AXsNw ür`(Av}̙@3f8rݻwt%5˭[v 8>|?D,|VjR! yskA!@E/k.pdDYի6m>z+W?|/<ٳgر~]IIpD"… !pqq+:w\w]1 J4 $$Æ :u*#({t'##ĉnnn ,8|0dݲgw{tR (,,D3xƍέYfΜ9s=q8A )))b800pB/ /\>iS UTyׯMt,wҥPPBڼy CϜ9x3$PmA={6==A`; h%^wAСC| ]NNN/ڸ NKKx"dCXރmݺuG X&Z`0yA1dȐI&-Z($$\. Ħbb͛Rt޽< Y@}fAJ 'W4ۂ #; 3v doߎ 8?k,'''Haaa0+77O < !A۸qc@@MЙN2@p`Dllljjjaaa{J*řP?f-/`9Snnng͚5(pAЛaycǎmٲ0uTGGG~'N8w\ jZ 0Rhh(˅i." a Gd+V ڹs'ԩS.]qFrr2;w` Cl>d/9rdϞ=7o^z… !Cu̘1P\,HA`YKAA xRUUUA|upLHx4r7J˗_|Ҽ'N@=^JF# r-2m۶Ad???H靝y!/)wa"##oݺgM 8 f#/^(HD"pj 8d@ 7$'`xp4İ0LH0}„ @h5C?]]];\WaYY!+[K2Aн{.a ˗4;882m(}}}=<< #|ۃB6X,V[a:̅6rIVN -MydkAdqAFwMHAKJJ)r$@>`hUF$!$ 9 ̂V"tndN 83jڼfȀf0@KhKucY @٠ @ P|95 jL%uAн sppXF n"pJ_ @"hQ-A @"hQ-A @"hQ-A @"B%dю|R>/Uj$J0^!f1bVe) :+1E!QRDh(R=R>/%ۉ-Uj l U@nA`Ti40je\6_ c|EJFW6)sU%IԄA#~gԆp8'JȠvA{Kil[J%2~ m`ݶ/qzLL З 90ys=Er T)9t@fal:[4-ZJʧS`3):9׼GuK7}f-k3 RfTXE4Z0J7͢%*( (}#hf_e čn&Y,Pa2XLi[Xf-2~i* B VA13-KcJ#Ѵ" }ˉ_6b$ML%ȎA`CC'vR"[ߒѠ`^k[#  9"ТТt[?ZAn GB mhQ-Z$%%ҥK ٸqc``gΜ9n8{{A& 6 >=zĉ˖-OJ%A*//OKKrX,ޱcGPPЊ+,X0k֬)S?|̲mY,m?ct[^vZ???77!C8;;,_|ΝSNEEE۷ogggUVV⵵UUU ܤ˗/d2%\R$mذcСl6{ɒ%[lH$/^ʺ{`hw1mLL - A*ڵ S1dΘ1 8wBϯAc@ ,mc}V^=}tw IZBBF؎Ǻp'O]WWÇ04id NNNv3g(kn{ #H~'IZ;..رc[n]x9CY1>9sfpϞ={%vKOOOII>$ 8p`۶mϟ6jԨ#Gª`+x|m7l6|p7KBe lRkHH_p̙3'N8zhH!m R8ppH#9([AKUWWr(vرn:6fϞ qB0 ҥK I8rHDD,j1Gz-USSh$Ihh(С=]h85C߹s @]ABE @]`H.eff-!fƚDX#pppX,@X7m 駟z{{l,g uUA @ ЖA @ EA`i 0J|'ǑJ>_ɩMsB.CLH*Cn078ӈ @ zI/VLHUko64}UK XKP+ K3w*OL>5u-ŒE(uJ>ObhmdPi4aW0->NJ>'kݜTh\y{E`T+zC@xe PPR@gZgpFC .֐ zRWw2]bJ#G~ӃWP MY( 0u4m%Րe0lhbL T:;JCBpa@τJBfXpzlNS<HE(D@ybU3$jW)hvLH7mOA /|  &e5!f,ؠ8\5@g'186t75&f0ugfhʂPm5YthDމRZv( *Y:aJj T3,6m 5&9JqLa*qAc{(8r@(4Ey2.W-fC0)0KO>(:00R W fH9t(r>'aKZ  LbƤ`N(`$A h!5FB9-$9B&CTZsB%,F1_ѪKgQs%7@ ~J:D+(}C+of>x1Dȿyr3Zm;L#Nca3zD!F&Ok^RZ9οcr5¶A 18Q:(I1uQɑj0d1,-*hoAj{['Q\&r(UP&2 &.ՙjA? 蜨Q)?%Zx3`l V F"8#E-+`Ax88k:H R!E0U/w/[je<-M ɢ|SEy.6!efP!2:KMՊa ZrLd |VAS T+S>x[59/V)L!PAĤ<^ަYunjo=~J9,C^mPCo-A {[@o Hbs-@`ry5ҶA@ "5(He.`Hg0/3 5|*F2h#lSb.E}SHRYKSS~Xbi[P kJ `u G$OjuL̈Q9I@.jEAjØM3'MocHժ?r9"8?KW?:1& kLnf6kOocšO A @<&ʩĐ.y- Ж(_zsbmAЧu:/4k֬kzɑw@мGƺwMtr(m͚:u5ŢgxP Rk1k$t:NY...&ML{3f}HM<be $:Y#9JTBuss3"GGǻw¶(:#ASii) @*,Ye Agt2wy+]vv,Xk[D:qP{f*w`Y#K_ p?ڵkVc']m'wGu_vTQQ=zެu޽r AB,X>t:GGB AzҘ xJ;ɓ f-]t߾}g`edff!,$GkȬzՙ x7T~~> @,WWWco ݨ;wvp~{x7K?~tE!1c9b'ocEJx<'''\n|0!8l:MsB[dT 2s3(Ao*??ժU7oޤI_Dd|vFK|&Oֺ9Ѹl-16 ?60НAit@[3`GGc, z iK! `-4fљB})&tv$Ub6 |eee:ArFB0W ita y2.Q^-fC0)0KO>(yGǣ”D@W 'BCA/3x2XZ [stLgAAc Z`Ҙ A EVo֭և900pϞ=XḌ@L'#՚r}r b2Y [T`ABΠqkczBe2 &BB~ˠ3P 0,40A}KD L$%*;;1,,f?~%\jQ J84nI#]@*a8Q]p_I@"XqYrehh(V( A^sNܺukܸqUUU hӛYzNYYYoAd0vmƚ9!P3QXX}UuuuTTԽ{( A2C--rҪUv܉ AB/oh:/` R*...g HK|zj+>V۷o[~>988X}G ψ~z~~>AՃ:<< `GVRY7! 00077fš5kv؁A )@{u6rEP{JNN;v#F X,vvv^t\Ԟ.\؇a 0J|J>_yMsBѵn;Rg2E*WrE|;XAA%K, OӨ`ǧZ7'<W"l-16 ?60НA&?~|ՔwͪCt5l%(yGǣ”D@W 'BCA/3x2XZ [stLgAAc Z`Ҙ A ijZptVJII3f]Ḍ@L'#՚r}r b2Y [T`ABΠqkczBe2 &BB~ˠ3P 0,40A}KD L$ ̙3ƍKJJ~뽼ܞ[kCvAMD5 t˖r')*A𬁲AJD7Ur  W|0NRSbXg /YB._VA0{lT^e# T\:Vr م eOX@m-Sb!.ÛFJY"L넒VXSO(F:#QAQ|y{LA *@*?CTxՐQɶ-0_`$-g0$M$42%w4h{5nnn6o޼{nA7O A 2@VT*3;ԾxA @ Z… X,MoSNxb AJOO8qYl1q]Seee_A e@6Sw҈7n`٦  눉7988 }ѓ'OVTc]H$"oookzC`@@!̜9ǧ=! 3fOף6ȏAOz6CC( ,,kΜ9?ݢd-F@0zX YvRu !P JKKsvvD1}nԴi"""= D>A`@t,~~~tA  T7V@] @ XtfM)dMuPAki~_A @(yGǣ”D@W 'BCA/3x2XZ [stLgAAc Z`Ҙ A Aʊ[&<'Jgb8)&֔ ǗXLb0آ rm\'(.`0Tՠ'\`񦹄["H``-% @X@tڵ`Cֈ!l((J4NTc@܁lu2ORLUYeo<.6  ` ƥjŰ@_-9&h\ +A ucA @-4*.ʿa+BTB t2Rçxj_ 6–)}uM JB#iJ%,NMeaIuBI@m+)'f#Xr‘ (>Ӽ=@f|_ @X'3 A 2u:=ve&tz ;liFmA x/Z=zhy9P-B m v͘1ٳ84 @ z zFA튎r{AW Ox_ @ !$AO`֬Y11122BtFMAmMo(󳑫 8 F͜9̙384 @!p,A`6mZ}}=AB{ׯcZy[k`ut ^}۷Zi붏d?PQQ}jۚSob+/[@RSSm+HvZMM :i[;㶏 w=Z RW^y呦qG{n Rl+Ν{Q4H$~'ݶ=bڲjkkK/= ஁)\JJ,ׯ=#y"FYedd :Λ W^mO]YhZF{h=֡:67xhmGCBaEhӳ^]e;2'NϞ{.-q~bo[=v/c ,f;...Vy`mmm~~> 5ƩS_nK}biii֧ҽ{8A3\]]ccc)lΛ7O,@v7xzQ{QFI$|u(!!aƌ|AgHfCCCu:]_2lɐч 0^F=`}F#zeSfffHHߟwoG ϐMv>z}xuuWrrmp& #oyJuIo?9򕒒 wwo#қ hyyic򕟟w^{{{_xu[ ClHIR/_~OHtW^y問>D-GhLjժ~IGnmiO>wsegg1}GGG|U䫯GƂPs=E⧟~ Ç-dȑ#v9zhtmmiݧ_~X6`'Jt۞Pmm-8/Y7AfOL ,sLL̤I1ʧO^d? jo+:h-u·~N6 )k^^^}}=zqTZZ Cxx8IC !ogQ@PAy@(mgX6mJ\/TWW? q=Ry  Cl9n FJԷz \.wZ]UU*ϏūV ȧ Z0[ ^1G :wǎϟOIIGCL׫C%uMDeِC؇,m], nnn3L@Z,(.]tСkz{{Ot@̷~iNAOJ3mA`ӧbh4pQyyyFFFLḺcqذa~)/ZZi*̠3?4/KYbŞ={N< EZyyy}TgΜ9pul?>ox&р*Zᥗ^՜ 8ԩS/^mTTBΆ؟>rg؇uuu%%%6K!xxxٙ>̿1"U{[o@|'QF1.CMu_x/^Ծvڭ[rrr ,C^ZZ IP}oɯ$I޻w`0@3^VE_nn.o߾qvmTzĉÇCFf͚yM2yРA_}y<!WJN ,N~7;(xtw}7bĈ &L>xiӦ} 5\.OLL$ 𑟟ov۲ pLp۪d J0@ iTPw߼y3..2Iѣ[_*iӦ7nС_ hY%i-ܴ[V_~e0?G~Q 52׼H_}^xr7o`DLO8/n Aaʖ쳏Y𷥛 6ѣ۬}״PQ6h])B˦B˦L?B ̖M)Bȏ6fӼ9[-}1[34`z.}4gz.F{:4;FqR@ٲaoEP(K(KQ(T2dGoiIC?~|]urryzz1yĉ6 8@vvv׳}AW̙3رcSLٵkp8kΝW={ӧM& k``ɓ[?^Br۷/'':҅eaWϝ;Y܋*--=yL&{nmzСݻWSSS^^kYY|.Zj4 ֕m~FhRTo&& x뭷b1r}]X X,W\}6Dz$]+kgo#+̋ P 7xC$ BR., y#^dA^N:5< e|& (,,²抠AA`~;' @ P(MAP$| 7QPǮvABYw蠟=4RLO)(u/*Kswd EJVM]uW,?@yEeAMTmm7aABYjղV,JOPW*W?r:&N-4d%ELC%Iޫ,//P':AEj<^팚JX=~&֭X]jr4w t55f-+..)e{5Ud:=95p]՜S"b-.GVו+ EڼT}y24Vݻ [ˠ2KKJ ꂂ"cm-QK}K7I!l6X[WRTJ]ocHKM5k$F}yE][}VLL*Gnؐ2鶪uvSOsuwrs $ UJJf g؟|maua @,_}KWW\9qY|Bo[|=]L7ۏ6l̡Ct@rh8Q̡~^ՕBմղk\΢{yN4v/sŮ]k?曒kAU,==oN \4}/w9ޑ/Zp!kV<tZ4e;~dؤCS.c]\8g2s??mA0 8sP巺\}-ryI'N0bsqgLY%񙵺~1\ܱZc:0'ȿv8W!l4]H/׳_2SSpwӲ(g{၂ݒ}IݯJ=4²{6^*_zءf-9vHIx%ϝ:6lHz,k7㯞>gQdϜ80pۺţzJ/]s}=em CQ3w#iﴉSN9#tyQ7?,[n"+)..M9i/^ܵc뙈c{Mp^7y~I'/* =pՙaK+Ǭ~h!kLvtp-yw\DgjcA|e {Ezn9.7l6a^y_;{4+D/vOEȤ[qPNԾZ8>|?xđ;ÂwW/ںGAP5=nfĝWOFNoI)%xpMa @,KG[V_Mqد㇏xME]J/Fww2|u[6 n doڰ~Kؑˋ Kf-ߪ)0zL!%U׶,QC'f*QfG6k<%0×}y3oIV&nX6w#\"A 5xys65hxzOsv- /*}W^>l;9h-R.0ssΰ 'eWϹwagɎf8Աg&OwϿk稱oi[85lxLrN]}ٓr,ƙ7:w$}Ӡ!/{)/Yw g|ܿ?k2ˁz=Y3v][ǏS!P((捼#G;q79?t9KWn0ǗZqCڠܬ-\u±*RN\n"(L16n>ˊ+Gn۳g 1G_U^a0Tm>Y\9{͘.<<2f̉3"/Xdzb4sPGǍBQ*}ݲ(=AاVs|Ama?x/X7ܻ___4'#nϿ?[ ^<{EJj w;e?Z>~JzN;/q8ErL7?#تQ_d4QV6nu_]5ʲ#ڲc޵׿ndkv"oK]tuٴiӳ+/8poG  e9 HD9sƌqr=zWy\_Y3:9u OLpPqA^0d{S7^!tӊvF bq;EDQ&gEr'zϘ2m׮[ψ#MԷA}me~z]WfkR|\9Ə?^ę=#NY~s9pƍu=f儔O_ w:ruk}OPVA< *U# qdOv !n} Rn :bጟX39u3gVU9ߌ93H-ض<``{0,͊_F2r'G[:yg6=AݐxQN!?/#+ g{3EPy9yyI7_-" y9[SӲԚ;zXY_AT߿W5wDz!IR\Z– 5V߯!t)ʤ̜| 6RRSSY9|\WtPuMhq'P-,͝{de!#׳sRvYfZv=XpW9z.=-ͼiMzDWAP_ghJ+K7oQkj rԉ7ofeek}q ڰiO9&`={!ZBVWWWo(/DM[\:;yPe3Vd ׯ]W$Jjk/O2en'/EPGow2?=cڿ(\ryTwS۫k'-W}M[ݯe5CyImM *4ћ.mz2ۣA/Ѽi7.}hW@ȃk GtrY eQ xJTWڻ"1a-qg;ZWWYYY[׳#?#&5:]1$-!P(kcF|֐ 5B! e; 8|{zy둑]{Iׯ_/++dffΞ=?LAB>?,YdŊ^lhʕ ,F.]:mڴEzQ޳gنCWv,44~T*=v˗%IDDɓ'w eŞ={` |=tо}-[f^v˗/ly_DP W_}5 ѣΝ;+2o^$Ax!lܸvlo v|>X /7n(QbbbjjjrrrIIIRRRI01//޼yի(`"|50/ _a_I_yOfB&x Hˍ+Z W\\;|חmC[n{.|{.5_il~=1|mYoiewbuzl)oF@AAA(%BK߿gk )s$e|5o]}}@иTTIWjo #7jĸhEVMn\th=+Ӡ KFMoR.is ;ʒA`(ěb!QX=~]8;agjgO;u/?_g1F_:[lӘq+jܕng^8sq?_Fl0-En)'X}VehYIZ}v*lŘOIJ5:(TvcL˗v 7˓TdUe؞]igr_zkUʾ?6reAb}6~_9q/ (RϛVںm5#Na8ۏdw-)&`ͺ̂BMȅշz1,$ȟe?wQ~T>Ñ0Ԑu+f1#F[iOEWp:AZȩK5)Lq` r7;,0}ÆU5>n 6ABY2jk Gwq|ΟYx݁vm[ΝP!l8sv^i@q S|6m_.ܰn =s-9aVz]; (Lc,[4슙9}~Tgm:O}GXɋoZ|s%'oy1BKr 9~sÞ]>t$^ӝ,:sFbBc{7{e[U]=F :Yj?x1^"c{eLHT*F3=ΞOpa..bxȕ&̚/2ۭ҇A_=ui2Yn7ׯ{>`ÞO*.6j"3%kA (̌ߠ?05`UYa7ox u?hC|BF{OIEP>4xp˚11!s-:z*1|5+E _v˦kQ\e!Uŋ7]2cѦyD~$Jb,vwÇ8:nڑk A>Y>ו!ih$|۪97ܸg SCfwϛqb^y s`g,^=ۿcjVHk+]]hj4CC⭋XމAk  ʞ![Ǒܽ7d7X?d$lɒ,YL-f333CKjI jf=Rkf8dyQ.8uuW]Յm4k;Я(bٛ`%qJ6j/9\^OY]DuV:Qz)3GR9(}o}TJL2+-ZFj A!:;=opjt#%*!>9!?'<*ï;6>#119:ZX24%&:o5}g1pWRLwoTAF, -LjDzFp:bcxƝ6m&=MnGAQ} 42JHːWN-NNih"Q*$졶ew3_tiY! ? [2b%TvvHU /ΨEUښ*khqsH\BgG걉'Yv& el.,[]ͦ-._Pؿvxbksi."u(IK1 ĔyEe1tG_ $H?P9+oNS^Jf.ʫ<~lGҺOH{kfp}TL']}ZrtU[#K[jFA>arN4(~yGg XߑT{z1OEDD'7z76iM324-\UTUJԏ,py?#sg ]rPɱœԬpp_[gheq~qyUssCww{.N]]]Y#c UѱE;.ylzKp͖‚C<^Cۋ}2ctdgfַxY;0<14H8Tfy  A 87{9 \kaY,X=\U__m'2^$fsS$LqU\)xpr{@!H)E/H4:| ZKYTp#pYp`0¬=J)< fw-/';kh7N7:ӎsR \#b2WCCozf A 8p};.^GaBq8@8e"81hbh4X*C I]CSLb>k$]A @ ҏ 4~jb1>>~}}} 55 SJJ H. ~tvv]@},A(\I \krrr @PY\\ jEEEUUU`O^^V:X_\ٓAtI@DEE奫p G --牋˓'OBCC]]]===bii | |.~f0p.CPHLL|K=?OMH.BJǸ@1LSK H'gqqJ2~[__OPX•7l6XM:./\y\ag_W&$H ⿪~.#y444o_z; 5@p W A @cs A @  A~A@¢v`ʉQf 2~w{#|\쒥! `2R㧗a7.@09XD".@[A>Ҍ A$ yMšAO:Ggy\B2ȧ\B&WNpX2 S1hQph4pj9ppnG$,ߣX<2'H$J)ӾbЙl6_H@-;Z N) Q-`0+$<`#`Q,b' G)"L `Ш &x.3_ 0i{{<4jy4q+ B PXw*$3Ylz)y=#b:˸SĴvt dcСC<5|C_:'ȼH H. (??SGgA bH_V9ɦZ"7Dt-[{g'Iݽy;%%|u-$4KCUE杫_}đ Uk$_7uu;"4MleGIG ۯ6]67kd1HnNA^RR7DD=gܝڻ:|[ rHP@mP{WTtm'wn_W6vlWW1v(AMNCOMuh (ɈK-nk41 `;H$"{W_M+NMrHK U1qqW5,8rK~E]ճ 1w߿WT)_| -fC6Ӟz޼qʵkҊk#u hG= A,JO{ޮꚗmIϚ:JkxED?vx`J~xSq"]V GM9YMñgfV.#Y˛}m[=<:"P6is͆F-)IcC %:ƭ͵Ǟ-/;4{VaRTZ抉fpr! Oy;Cu,Vc6T:w|Y0`sO R߃rζ } Ϣ3w]͝^Gu麢xpE]q{C+wn콝{l®쟿 Ϩ#0%ܽq{F܋SOa_n E"Ccu`pnkujn^C|D? % A, xj&39|hrI= 鑭]=-5zZbIG$Ƨ&I E/&GgMsS־wp۪rV1w[A,v>ӸqC{nsNsQtA~n#Hs pL^]]ɎWV)yGQ*ᑔ-.!~xtz!Gz;t5^jbC[W{^M=v:}akX_In`ae~HG^Qε;g֖k{fs?v,춑h<,--LEU0QCyHrnSy[Wn&15"ZOSD)((kɗ Ӣed$γN_ܽg%)_ pS @tZoll-Iy뎺ݻƆY8l-ܹͪ}+_hnn?6Rۦ^8҉WX(_\ꖌ06jҷeMv]EյU DyFL [L_\ks0UR}M@c11[/l ,H~X q9h$F4(> v,ll"Q$rRtK)t$Iu־1 m݋4/K,Wb. b2@2`0|yvQ#R8ٙ=zoo`8e36W6wQ,lO"[˅s^+H,! :yookkkcc0#X^[$۰M,u"sH$ǂa133 gkd$tC{84;988 ޣ0ICCcD|8<>ʓC>sSc ,yؔm_ 6YBl^e$ r|tX===# j߀2NO ;[mխmﲮ@ 78lPgYY|Fh4.|]D  A{ b@z/;ySTp@Acc#ùp }/ eE`hS{xxݍ@ .ش kjj PUU%Hw<@".s\ZZ Ax'?}NMMo)8$UWW,m/J ZZZ ?G2444~]\\5BCC[ZZJP舎{233j&' ]0~_PxE T5Aѱr9 a:~U}H> X炈H$P(Sq&J} JH_pYFFF6@.\A_ 2@@[g1$H.^C A @$H @ =!AAGV1;i" sgX `v㣞ϯ/gh,iA ~:yCw[˹A·776aca)ש`1! ۂLNR--ē~+f.AC\.F;Kx2]{ ,F#p8[ D<kw{kyuhzƸm`G`2ق#\huJҿ:o.ϓil|D"px/85DX][Z_/i`j^7 p*dokme L"I#e>:|H A$ `韝<(Hy(vg6Yi1j2wܕ4+oH{W_zGT+W}rSd%Eo߻w`+bht2+-û7t:۫*)|ʙ/^,/w1O;52w6;,lqiI~^cʲoHOqNc;*d%$ܖҴjk6RVW]3ygiZb#MjV}] jR$o\]Tin9^m`wxHY9YvwTrՅC:0#z_]im$*_D7D%$o_j8WJMNS7 9T-H'a*&4:s恤yCCz54SRX"7-f942%22HK@dpOW`J@I"eMk;7ͮlƸZ?yrdJdTcIWoP o~ߟݔZ; 4&363UV3Y+߇jV,!U%l{g'(.JJlau3'[BC<2luO6g1tVry -0&%Y%<"'y0?TgNlg˞Y]9c׈6wF]wl~a<@ 쀻G``3?[?MyHI;:Fdԡlٜ@M 𡘤{N9`S7vLU445 2%ܸySBBnqkg1?7WeamxnfrXXL'sar2R7%:FkKՕdU5'V X,֋81I)m]Mu=?L 3z3R19 :k!_JBBCECIYVNY?55/}wcREAC)11}K8|TIQKL{L&O{T6nW[ĕlXRa|Q uU9c8ng(*xx˧o\ 4UgHaYO7 ]<2H"SG7I2Dhȃ} MD<8 N&(là3{G&hַy>`oBf!vWD*LbCsL:%H#᷷$ @ * x\4 b4:uE2SOvaxfc$"Q}xp`T*$]a0b6vvx<^w]Yx) .ЗXؤvW0X[^Z`rϻ+~s‘ק\9zmc3W[E'Q@ iNɩ#ePF%֖ggg玵zo!=(;ZԵ '˻KYL/':yH@;]v3ײ#H۬ A{_)" \zASL@@pb AAPTTD M@D@pxxx#Ng8T__!eǞ`,:)\;Y,_3 &fsggפ 3?Mnnn+--'><$$$;;"S[VVYw{EEEk|e@A]WW^YY9111447WWW_x555>ɉ 3lEFFfuvv|˭ ᠴ]zHnnnhE @Rܭ&P\d@ %Phw +Pɧ``0D` DPKxQO`4zcc"I3P(` "DD |X[[C" w LK!A: Y|˳GY h{mD @tv@>D? $ @  4$H> N^γ@ h ޷}>v4#f9bQ y 2@ 9:56K ބ Krk(%d_{*f7,ejeğאZo __TDly No5=[y[|qxf]5_MV\$T:”yjG4A <@e1ZӜlUcCcHxgyNi5@X^_٠1X@noo bpD7<-܂Ix*A0Եũɩ驥 :#胝 2Jm!?Ea(g[ 6LE&#RXl\oO.f9a3f`{tavzjj{bmk @mo <>E[^Z1]# T:*]Q0|<1Ʉuá1CSd Y[^C6>>ֶābFuwucSeZOlAq3t6Euw =2yTP+ A:PPϛ7nx?PVTUPvtzD hC-%EyE ;wDDE]{}{AɥbJ?#b ;8|7V[[;}ih*(i[t{7_yڳxomTGeL@9[ZV=qQR{89hdQ=JxW V%h*ˈ 19'۵92rjƝ-t?3pA}^3?YaZ$%n`}ҎƊ s M'Cp抳];Cν⾦{2J7D6$tUnή/#wEwWcXdJTTēPg CO|#}g7v4L;GYllW_h.oƹۙY4lszY F&"}e&}%9=}%Fm-uoyPiDObUEg zq,r3W5r:r(ck. ɈK .<˟KL͛w|yY|lUyY}Y2j}o޸7s³tv^(ǵQ `xy'kɆ%eN^JeqN84ح(2!;BcYoIՖ߸+@KN^ AtApY>41=Vogi,&!\_<=0CCMUWOSN^%g1rVb-]jf?nb?'2RrZF1Ce7goZ{:F?_Y vQ׼/qYl`Z5J]S)*"R68v:Cc1?Q5䉫{EYݻw% óXPoyNFmX()*iKEƥN-.U&(*(>Ty3^W*/#kh;:AgbVeJIKk(fߺ)@LL\\% 쌞,rjI﨔RVTQS4H =t{@AV3k&&z?0K Yh߹&vO\Z\L!^+PBYIF:%${=SRQQ4Әh`GX򊛕Mz6]rx$HGe#hfPG x"b aܣ>\Ԝ5rRSf D2[=O (9:T>"=%3 a/ķx_wHb3i l<:L"Rt6+Sx$>[I9w\j6JcPp8e//J9!88./Ix,CyE9C6stMal#h'm) 'DOZjD~W pY||y G p{qVH H;bSi)1M@ @pvMe7죿{j0A%0v=,Ƣ۰)v֢W׳,^.j@ ҥzwub⬙{?:<.~{@ e{^s(!@mγX#d~jll&lA@nnn\GtvvpŪyGmP= c F+tLd0 j+@h[(6Dv 7`^s$Hg~3_|Q***zIjj) *@/++hrrǧn :hjj(K 2z{{x4Xl?N^;|}}zJJ_^^޳g&X~455!@tF׿.((`0oT?o([vwwOq.(g@!D"/2@湺i#HJJ:887'Ө #a |O+QGCt\g@ 8g1X|JNN}?΢@ eм5 _й! @tI@u@mkD' Ч y Y@ % Qmc{ytNYL9g1IG|\{WY)+ @tA@!`$*CN&v!vx ~s$IȲ={qj\2ay\qW{Z~lcƼ~A𺧍b$\ ǂ׶+}4B"G_ DpG:vp`($H?Dbj<*̯ktv4 @ x{l_mkA#`18<ši8Bч8DOI'hBRAi>eu*t*DC"1Ax/&,6(341zԃ$L &0BI!S a"8srXL, `fSFD"  D7g+*j^n͵6GtE И$3>9>YR\22 C6Vϯ<RWYXX8杒ڠ.,cCLW|AV&8@ 2ȳ7CRu 3ܹRJIA鑃wxey)؟[^WjokTH}7V< O$FV>0UVw񋜟017ـڳx:36ǠB_4ug%+*)E&/9ZvtwF3ضVg;Ke%E;] to_HϲdA;*v_fbm1*/ӥbn)'a2`hf>;k>fԴMGfk+n]go\W0.">4l]eXAcKq%MtQ{@Xҷt{(5UsW̠^>"wgwO "Kc,rsp'?ϼ,z~!F2^S5Ⲫ-Ub*0;1 Y#eY *JzƦ壽3z=\L$"RcSQ~Ǡm$ bfl:0nmuE_:6Oa=ue&zڞ!K}~?%`c!L:[cqmNBaDtKCC]}ޜAQY_rtu074cq},춡\9;Xߓ3ǑxJeNR 7HGܐ^[5,51 HMFA^i @tASRў.:1!;-4'÷=,2)661*KM)^ N3Y1X#" l<@"WVSkZ^AFQixQ8@xo_0վͼs#j*ŕ-:&M.Vmnu]+I9Z1ciM#ފɯz/aE$Sm$-}Oo*l`KeUf3m45-k+KֶVD}K\yCDc PPqpcO>\ރ1 ɒƞ0ǹ/ڗm圪mC!4:TITcYVfjL`f%)Ovp@  Os'.v&.ށu /C\]]=L mK<ut OΪB獵fWԯM d'gd ;}]WaцXziYe?W`޾^C|v9uc%:MLJih}uaamWZLv}(1LfZq@GC~)kG$g3upBHT:bnoihQRZwX%%-&=SL0i[:4}xxH G– "()l6p,p@sp;wNMMe㱂&Mu^@4 WWʺ Q}}ӧO zߪ*P,//B)222-- N555ߒ_QQcnbbd>asssSS?K #_x1==]ZZ:00Mx @V`}tt`oo| Ν I\Rnn. yzzB  / A= D܋ CNqSPY͋L0Www݉-[߃ ;%xXGfCN*n—EBTہ/V@~w 2)飴@Fi,$HGk@   Ao   g@ 9Ex&D[! tむä.H0C@cRf $Ǜ@ NLNX}p$ƅM A:-a1X:]]781)d"&(<>bRH$K28p,`0X,[iG6lBfDBQi >G"qG$2EhpƪJ =4*8>1YtYv.v}>=9Z<-5)+;01!odaun23%5!1y`|vDUdzɹ橉ѢqU {myɉ y%##c55;<qj~e|?'+3>>qhlmmZ9pX]][e o rAvj\\tUc|{0?$id;sť5x2 VUl[SRfW+/4>8#VrZz(t։ck+* ccK^(ttzj|\TTE}'8\NMREe~~*H-a{r_ɤMpNj .5_T^!A <@2"tԢbc8;jkćE&g%;X8;h$dFGK>/+<'!})'' =s뻂wGn1qs5u ]LF* }߫xo#FǐV-[ LM˪_z89?q4V3mu;v(s5~9L\]kZ#\]ĥ_v' ^Rr^F3RR<-5985Pk%f'z u=i+:- Ճ$͎koظ-U-ޑI's Sk{⪃ [oV^0{tƮWus{׸w%ӂeMc1 e }բ/oJE= Aq6fVҊX$H AqQҳcf&FDG? v ,/354{VMMJ(] Ȍ WOˎ t3z3j;=Mw➜$}yKOۼ潶i&&ccjlf7!"@bߑ%*v 6aM /w1I P&cܹ7wfDvy::{;cS7u?9odE ߹j;'{͌ӲH1 rHD˽>!)OtΤ=\C1%%{Juۚ|[E`SkF.PBhm]Jj?130L}J)ڲ˻#.`3p.^msktcei=֬5t-4VU^xZ"uohT}D{5tJ$!taYM}fL+JPvǼ4"@^x)|J\GpuO,`f sS rB|BQi驄\J#W$755һS䈋F\=Zz$on|u\Z^\f$Sr A~I&th+@F-l|}8);WJ)a~2"8vy @G3FfFNQ驱/e t7wwC""5%`knKB&I"QmafRfaD\]dc1!XD-#\]!$jw 羧sLLL|ձk~Ju..'\V\jtuqIK{;="LI``jxkDT5o?\<yI"؎TjS7Ƨ9jгת+HȚ`.>b=Pg(ןsCjmpc}]uv+pvzj䗤rW2kB:~όݶݑUd%*i f<( Q+D&'ճXTnyfL[vsEHW >Ebj F-T*+^[bBRnꚈWN)篾Ԙ>wj0**` EY|=8Б5@@@@@ ߾5ݚy$&W|E|["phaґ0pVQQFc9";;{ffiP-6;;+H@S,V$S6eX@KKK[`#[,nOt [ 8W`G =j3$#P>Ep1ooV򄨨(Pauդ$Р" G @kP(pN PO5|GGR?) *sBU7Xu>&P 3@.>:y򤽽XY|gΜ >#xI٘ahb?=R^^~A^"@"8vEEEK*zJTodۗ$VRKl"@U/HW+R|.B 0fF.XX쉶~ȹ`w"дupW$\(䈈`]lyɤETfɴ.B!p9\@jrP_ M  TT*k`Uk܎al< T *֤ZZ /-..-4YY^D[ѐ%kbdJ[\r֤r☫G|47o@e VsUׯc7e^4J2loĒ,פͣjD.Pjf7U)b1Q+e ss>V/Y  1r9)p䖟sS;~w$s1X6&I X-K27梡yscum] n3f19+2@gΧՈ;sL@#;-=GĒ,fTN1Κ̌nuMLtz'1e#+IW]ӭƆfMTzH7[sW&9#7s;q1"jyπ?ഹGHx"PZ޽0=eu?CX7, $~t܊$1HI7 #"_,4i H;Q'1k[ښظx\ stD|tt *:}5;:44iP=7fu+֛Wop1oY""b7c Pss;ZR#T%¢=y))^.Y,3uϞu&.`3WDۏzvdtl 6avQ(q޺73Obxyrě[>ClVM}'n2 )YO>=:l+=ySՌI`.|zyfy& aPtmWJ[U݉͟@meou.pWVI}o D|*u;~_Kw0#֩6"@hEZK]%.6斱sFVjZ0'{ id`y,1h>>1o0gnk2ڡU,UcM(q ޵tʪ*,f7,"" >妵[IQu!gl?w%>)u 72 (7_&O6.RE?;Wr>!qENZVhc|3T\LJ8}^!k{Œԙ˦5W .()f6.sb9{׏]GI9:"Xڛ݌܂ΞAhtwwURJ*++ޱIL&b>opp8? clW8JYi" L2U95гb(/*mmt_({iR]^P|6?feu}O )f=E!&ʨF)I/ubBʯ}o($oXCG8ANND"9]oll.--a]T`0B!N1_*\.VD8>>=\}$X> F~?gsEP(e2hTbrs \.3@('vXy ^9dg?366 A" N}}} P8 `NNNVJ"[kdd d2Grr2BIJJ@YYYx<`u``OS(EW^WW_}'? ÿ\ɏ~W@@swy~7|7xw_׿կ~_,[<y_Mp*nY9d@ G@ P@ @  endstream endobj 31 0 obj << /Type /XObject /Subtype /Image /Width 515 /Height 404 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 9305 /Filter /FlateDecode >> stream x\SI2Ô=d Cܠ(*"" Uց8ZoU|CzZmmC .T y *J˅yy E<aH葇Ȧq=}5.IAzldqs؝GƦѣsXZ2x<>_ haAA>}D,DB@y\F7H7]pPtX|:v7G`4$~ ꩧg̻@rper#3{Q&Mzooyo+/wE9 H u֩7JHrcB zɬɌiɍAG'$L_oNOm{4oԩFXe !c -<ȣ'ry'Afd9afΎ|KW*j[Tjm,+|Z9wRA~9;}}\n1l%^|_Ճwkk,v ^gצO4DAN^Rs\d^IKw%%W=kz·?2ϒ.w E QxK.ߨikv/3.؎NA$$^o}|=v7)..ޕkϽ}_'cDR_/R˃p|+3!Ȃ^P|.: @>W>O/)S=PM/^8whXD̀g:B7s:x-嗊̛#7ND/?D/9xpo 5 3~}R?ޒ%3#HܧmYn|gvԐ^ 8 yBTf_K}1x*eϯ.nj(W\z%2E|W>UwRrxcHdy|ld'I;cbq 8 4tWh):2 2&nj۷90,Lܑ[`d:r >`if&d*2[LM` {M7 $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $@H $s '] 0=Ԯ]3*RJLEEݻ(ScwΨP3#JOTRCy!'ݍReg,RA޻@U? w+ 9gXETFNQuNf8*bTDLO`=I@RQɪcǎy/XTFFNԣ,(%S9v:c"W$/V ler>B**uT@~?5oMvB.d) 0>< ^ T(RyD}R^ DuFœgӀ|:vRAOV6*=[?Уk=Թ dٓչٽg]'<|c:?>X4 칀Ju0 p-oOtP8⻃1@H $@H $@H $@H $@H $@H $@H $@H $@H $@H H8c>B:WG@ X F\@-Nu:/E'BPw^ t~/ǰ&>`㻃M|XУGs3M`Fn O u8K N?,]t:oLCŸr!M|!\8 DV (..8oN:.E些ܕ G ïnf 1~Hw0zP -n )JϾ 8ޞ,K!~\we,8x}N BĄ&`hI*#d~$^4& JQwALN 06.cN!M@Jz 'K <^/S0?>`43h" dTeh'R]ķ&$zج7??Mu^Kzw-F5$ؽOԺj^i3eE5ۈO&Nx#47ȸ u6L3ry.eyvrf vH@b:l'-(r}KЄ-[m-ir~|?$L1͝㲮3n;Hats 9v6ɡ$0kg"c{ؔ ;!l0#ƒ? pr[ȩ0tL(S^O'f9=_=eVa *4x痎l p<6-1Q‰aVRA{qƾjH=@,}cK̜C蹜H[P#%Yf\yKO 9af#[?(Z'M vH<gh6>eMo\zyCmMD9P`6nzz^o֗<qj|TwE`Va`;Wi:_O+ٵ*sVBLT"лuH^Cc+<}V۴V~xYSF:[ɻ/q) r 6B+ϨkFVYR3[7Qg.-{竵R}3$ n/=.i7tlZv@hKWfKhi$~ eq#K{ѓS3V/oUrDhў kr͜:>jTu,6ܗzH]ELGN>g5=y˫5}egzj㚼y)D0Iz.s &Va1IfztW>V[n^rYzj⸨!\,~Pͬ=BMNd#n޶oKk({p=iSFy;+,n Yb_H䘄9Wlç.Vϐk}֍+-=}Bl`;KcC'Rd3عKL(;mOoʚA{cMUn(YF4}yݴx>Oh;/mZjYI cx:)yiYObpr3~R%ko*›~VS0z~fN<>6b@̠B̧DIm|E8%uGm,9{aEu5?z(ܰvU^¹)SGGY @g@nje?cPQqg_`-$R@Jh*w_yhӆϜ7::,ӅlT$&*T"q2KO`ه]tlIEMm(gkuWdgfI:qlL@o7GR:W4=._lhdne?$,jIRf/Zzc=w{µچoF?xm׭Y׫5MBkc]MuťN=έ<`Ft0KR Y;{1&nҴ3-X,76mٺwO*kjj5<57TWW]/co\W:d'?&&rX\. y]?#JK@fjnpr% 9~Թ鋗\MEw<;sq֯]*/{٩IF tu`,_g\uNC#G00{%Y9y֬%-l.ں}]Ͻ[_~Ͽqʵ*X*eK/|Gvܱz2+r2M#?a^n KscAw [QڤD*3ptq# %{ a9znidQ#`t4dXdٖMO9g^Ƣ̬ܼ zt lݺu۶wF(**,ܴic+Xze~^nEL<aC|<+- yd13T95>~i/\tYNފV]xjKFo˖7_nU+Wde.&I qF hgcIO<}?6:Pnl6iϠaQ#bnjO<5)95m֜ -&|yފwZGd/#0#}ޜi3&&Ǎ3<} |wٗ:ۂ;_TgBz'"( 5.n)SSfΞ;?c%Ksrru7z˗,\hA9fL6e2Q!C}<\:f_Jg9(tF&l]<}9z S&MO֥ԔISL41>nQ1ёa!C sPXYɤ6eM dQڌ΀trcs:{GWw/?#9j̸ISZ;=99%K4mj⤄ qǎ:tp o/wWgG{[2rX$qzc@lT&715sp3/0tذQ#Z-ȇQ##‡ #Bv{; {3cz|/s×YxH&36!%X)H dGr 9 K`xdTt6v5|xdxذy{{ tv`F9dIf~92cDR`aiMbsptH9:wmx{{yzx8;9+VdMdܓOON^CE 1$K+kLL$#2Ɂ5Vdԓ@""{>Y7{uz[蓍S[dr#c33MֶnX[YgĘ*3gKfy㲜eY|mYh骿Μ<{Iɳz]+1=ܧd[TWW(˔e7**no4WxCh^/٘` E~7Hb58C%CIa5H'0 ||L0$H @ $Ht?,f^@,7kzT]YSuUU|" ?,(# M=kllh?|i]e}S/jZuRYR& 6*Q? z")~|\XQG`kC&p:X(Y_NG|ȸn@fVve= $"@ $H @ & f`7zHA"̬X[zL6|mcuF.]O~7?|}淒DɕpɈ "R}ݩTV*︵J(S*zOeEiM~s~ͷ?/Oœ~L|(5;bNܪڪ;.jCv^zr`tmT<) eH L0$H @ $Ht0^khO%fMm]jkkjkN#_Yi/믔W^zbuʲwUc5H@~[&4{mmov_^wP߿Y<jt >oOg5zFПlLI!& $H @ $Ht04P񵃪B~mLp3?kkmklknnmhmnlihnjhj/Z?w.q=/ð t 0\aƻH`%~MD]O`3 $H @ $~REz斦榎7755wbFmkDKnluǏ:zO}yώ3 ?U |ѹ_2I&'O7PseW D1/{-͆CBkQv_wF-XƻW :05HO {N@E $H @ `M]k'c+]VX_XW /r52Qi2FMwܼvT,Vvr뗮_r^핃˯a»J ߾"pP?9ܳѸ|t'$I=/!$0H @ $HL@t=.]/*7eUNeG#;13MȻ77o>}{z <׏v@?aᡝxJ9. ,41\'4}(a}բH $H @ I!.$qf>h|vQ32 h% (쫯{_i+>}=O}qv:y_>ߞ8{볧>!8h&jV.[OY޳ .5ٙ gϙaNzƼ,8} .]yܙC-p& x(/( Vӳ?^Vn7qu0:1ʌz$dn/WrT"ր?Pa,Ǘvc=b@?@?_. endstream endobj 28 0 obj << /Font << /F26 5 0 R /F14 7 0 R /F30 8 0 R >> /XObject << /Im6 27 0 R >> /ProcSet [ /PDF /Text /ImageC ] >> endobj 34 0 obj << /Length 1925 /Filter /FlateDecode >> stream xڵX6 Wxd'vh^vۃb+0"e;%(ZG*^=T$aQI2Y4ߖfrlfK]c^IZR$lªY.'9ߛJ|e-ź$`>$[wɶ-idٚU//Wi, ګ$.V{[:$y q.J!('uKE=IHbg,f8O<3U&E{ uV;U:?zֵlYvrR [9^7!ڔ\0t (lԗAR\b 2V׭rqJJSkRҶ4 O)- Bd Ƨ9pB<=66d)lۊ6L!J53'ˊd~51H= VlkcXty-pfnC?txp<Genníb],a?<%{ja&e% )Ȯ6?7:!G+a2mG贘I <F4SlAj[ e*: T'˳rp( n4AgPc@ei4tZ!+7!J I!--j1^b HSʊx[@5nliN뽿H@[ÉG{d GumcV+@wuo~tRZ̓ | c7bJ]JA! *ovb20 ܚ#Yu7ݨ $UTq̟v_CK1ڐFpAe[|JT{6u){ ?NI;9/p_;s _#\,prp|dS%86x V uh ]'b".'lƛkbu;iɾSH8u셣lAY#8d I?V#`>0QAIf#8ҝ9~th0Qmohk@ 72 2*fox |I,[|66 kxGP!n`NG8AG~5aUx4Q'XK4'(0rƇKdfd%v,!%`;EnNB;=p8o;>]i|,)mj$%lt=m9ôh,zܿ NJ/4=_xYM/ڋpAJrz1^T&4U-) }['Ό u ϫ)XZl6鉊ж<і2+Hd(7!1$ʠ;3b".aP?bа U S5rtʛGF^]Ȑ$.0۲mh;2` ^ kH6*o"諰ۢ@㤛GK8"LWjrxAә!- se?tp'-ix.d=d)Q#xj( cD[GieuӋt¿/ <:[)2bsyfbfy*}ZL?|]S> endobj 32 0 obj << /Font << /F26 5 0 R /F14 7 0 R /F30 8 0 R >> /ProcSet [ /PDF /Text ] >> endobj 37 0 obj << /Length 2061 /Filter /FlateDecode >> stream x[Ks6WhorfLf6Me2Z<SBRo(RhVmM [C7zHcMwH P"wKz}K^? vUfo?x՟WdᏴq"gH1-"5 oW EH-tLfH+>Ef diD#e U\=-v,Y|WIo}? #Czz]ܘ>Tbĥ3f 6bd/~D,Uu}w[؋$ B_$ƹ3W:KCrJIuE)8D)Y_ͨ}UE2 )-I}R"Ojݲ>) JڙiYB08^:[gJaNvv5!dYr}82lmW>I7e'C#~1B5Sc2Dki愂,"`*ʆ&ڪV*{wWrCѤR3% $ ,?fR @br-j{r~ef{D,j+N"4ZHE _q I#DNb{GFZy~twN11w.}&@RCdX,bC:Ad~M.ft&|swdn߻]e1M]q b?/ɽ;.%&Ae,V!(IꞚuIǀPRA ԊD! d3Ymt_k!1I1 x@@:]b0vq8_lguyvxݚAwW`Gs.ft`iv:j:8UQՉHB>O[,4b>j@k#R_s3״.N8єre98y,@A#6 WӞiOxX[F0CJbboc+/!XIxt$sxKurHт+@F\t\ċ\%"Z<\%)}b9 5Ya0 5 e@ 簀y;\LXy7Xhq]!4_62:VQPk'M,C@sxIkLJHk: q͡TYu>vRRBDЇ^·CjhJ7V+<ӕFjbڀȏ}^àδ3A4tUIOֿ-G;ܼ&1>@O5ǔ"> endobj 35 0 obj << /Font << /F26 5 0 R /F14 7 0 R /F30 8 0 R >> /ProcSet [ /PDF /Text ] >> endobj 39 0 obj [278 500 500 500 500 500 500 500 500] endobj 40 0 obj [777.8 500 777.8 500 777.8 777.8 777.8 777.8 777.8 777.8 777.8 1000 500 500 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 777.8 1000 1000 777.8 777.8 1000 1000 500 500 1000 1000 1000 777.8 1000 1000 611.1 611.1 1000 1000 1000 777.8 275 1000 666.7 666.7 888.9 888.9 0 0 555.6 555.6 666.7 500 722.2 722.2 777.8 777.8 611.1 798.5 656.8 526.5 771.4 527.8 718.7 594.9 844.5 544.5 677.8 762 689.7 1200.9 820.5 796.1 695.6 816.7 847.5 605.6 544.6 625.8 612.8 987.8 713.3 668.3 724.7 666.7 666.7 666.7 666.7 666.7 611.1 611.1 444.4 444.4 444.4 444.4 500 500 388.9 388.9 277.8] endobj 41 0 obj [277.8 333.3 277.8 500 500 500 500 500 500 500 500 500 500 500 277.8 277.8 319.4 777.8 472.2 472.2 666.7 666.7 666.7 638.9 722.2 597.2 569.4 666.7 708.3 277.8 472.2 694.4 541.7 875 708.3 736.1 638.9 736.1 645.8 555.6 680.6 687.5 666.7 944.4 666.7 666.7 611.1 288.9 500 288.9 500 277.8 277.8 480.6 516.7 444.4 516.7 444.4 305.6 500 516.7 238.9 266.7 488.9 238.9 794.4 516.7 500 516.7 516.7 341.7 383.3 361.1 516.7 461.1 683.3 461.1 461.1 434.7] endobj 42 0 obj [556 556 167 333 611 278 333 333 0 333 564 0 611 444 333 278 0 0 0 0 0 0 0 0 0 0 0 0 333 180 250 333 408 500 500 833 778 333 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 921 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 278 333 469 500 333 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 480 200 480 541 0 0 0 333 500 444 1000 500 500 333 1000 556 333 889 0 0 0 0 0 0 444 444] endobj 43 0 obj [667 722 722 667 611 778 778 389 500 778 667 944 722 778 611 778 722 556 667 722 722 1000 722 722 667 333 278 333 581 500 333 500 556 444 556 444 333 500 556 278 333 556 278 833 556 500 556 556 444 389 333] endobj 44 0 obj << /Length1 1652 /Length2 7931 /Length3 0 /Length 8989 /Filter /FlateDecode >> stream xڍT6Ht)HJt, ,K,%%tKJJtHwHw# 9{皹f枹yYFZ mN) 9DCppE2<@b12@vX'g(&/@dA$Q(x<BX.ԜބV3]h@sE;М++)B՛H}AX\Yp:eeiǣ LgjځѤUc t8g c xy?-/DJ r3/>WJc*Żo2/k2Tu]Oadx/d9K%?#0d73;r<w:N}jey,ھg>븙+j_ƬAMo|5\3ikH`Oj zK)FC$ŚOI #Tz2_ wN5*{n/ގ/, <.ev'{nH޾]V㐏C}!,NT2mE(^n_kH0F}ު1&K<"V\! `@bv[JN>g%[0);mⵜw$CC$ Rh?؊j>lndz i,p۷w1|cZ חgC_p`%Pge);R%X(Ô?0:(+`6{čK6a3jHy esR(rŸJg$Bmh?;EKB_:I Sj>*򾯻9/iI^6pG3E#{6孀% c{Q臛Q1ΔpJUl3tW~=m[ jLo'}I&D(S+G˞%/)3jWrYɦ>EbmG!Ro1BD^^EVbYIl=/( jq9޶6wh-sMd./VDeOj-F4(ΦL%%Ȭzw>Wfցtfa>S\=dX# I?x\Qb}uzV^s:*GpTTzyEQJwe":>n.vXX‰OLlpFS4[WZg-|F !#cFuFj\{џ>[f,<3˼f2dXK.3KG/6?J6^1*3ROA4~0&YdxX~i^7>G&qӣep) 2;S̭d]cm*"AvGL=`Ċ5frJ[QGUSG>`7@PP8N[pX;܈u/L{̬V%jGw2ɢvVdʚ6&FW3^=14fcX"H|O2JC3>ֲ aeu L'6avfSQ(%vJ0 ;C%+A}11pLJu\kPBp?VED9 hVTJRn49^< Ou#Ct?p@rUۤRF^H>]t)>MDm1M@/\sHmw~ [jb TAE/HI.$ޠJgaZI!O2ٵ5ާR-Wax˅FZ{7F| r7oV"D.]9AЌ#P* P̛tz?ۖ;K~U"[B&J~b{#_IG>=ry~وcbkDp#8JdUX(YV$6[kꓹQ#uv>ǾGp4LĮ+!H`B.~[q\kw9w@H:- >ϐH\Q>,hъ, mIm!3[J}ֳ{)YsB\LCu멦&gk+woA8/[ÇOn8:&$vKҬ9EqNAhl Q&e1_(Na&DںmKuh]Y+XtX۞ؕȡq|# OfK9qX ~FTj 8[?dÍڬ\#p'NjHi4k&44!x'YhvA3Sil*#ñXt>:< G 0U tx=ar&ËV lRf7o-'-\R.;J=w=-$O),|L݂fÖfHCJy\gKh]{OW7ݮ|CѮէDy2Ҝ|||5d&Qsl-%ǖRj_vA[o|VVA|e ;iic9 njUJ5ڶG'DrY߶1ْ8, YK' 50Ɵg[Uj9Mt5BOnpjN-/M 5J:n+ҟ?G\vsn[v/6'P`VQS2 IM|4Qj36CxC.sM֪ @Y(V^5+ V[A^]ܛ.%}S{KXcX }5'[Զ4|dro.C  2nUt9=ϧ4FKw~(M:5NcsIXͻ.C~uLǘ~}IjG]C¸'‹cL{ɓW+%u@q#:ii{E#C>){el-2q|15u0QLba.`T+5 AR[ -}AK".u 85#޲ ui=67VB[gCW>1m'L- i7i-xYqJLӌd:fa}Zt ˤ8FE?fMr_xqWXE bdÜ>ffzb_EpaFN +TKCz/}Iᓸ+;WR3{+5E;rgCpE=t7٠mxY#U\ewjS;%vH{L,9$dhӦqx`-8FKL&%}l ]/"c@syoZ@t5+cIm9ZHu1} Fr4>5nsAy{ATO2KMk%?,DώD_Pe~%ٺs=lئcbl$~2Ǹ+qa$zMF3G]sy5$i:S+fIϸ}d{YwdsȩyT1PrmF{Ԥ}gR.De>>umگȄ|N@`;ֶ4|F2kf߅P?vo5zڣ;,Ӎb81+*{CR~/&ߠ~.s<.\=%| e_/h3 znl¸ۗl&Ԙ銛E @Zp'@]-;$(Wx {V1n$w []/uɷ{4tx\?G tԒ0^ܒl>i~S/ň†f~?Ǟ}˨=~.eƉc|LSn dT1?Z ?%ΪbWmQC4mCKr4`A g[QOWhG$+p ,?Zctu<6QI4H?ĭ[M1R 1Sc1eA$g$L|s;X(cKɇ 4 Zu>+;xJ:8m[^Z)hZŨd՜ ƪs sL4oq5<"H`oc 0R4XFS (MPl=fG]}iӯ,6)l`ڤIyxR'[yA?G9Y e쮩j2HPA<%e1@JlO'-jX1]ڊyK f5uStB=+RG} Sʓ:H'$g\"=.01;SÑO:Ǚ5m;)p͒@\eòJ1=\{Ax\%ɧiQ4ɢJ_]9+=e\|}7!y~ ŻzZ=!jxC3gD¢ V;.h(`7:{@*N!xؚ=-ZeQB>fC: 5Ӄo2]KCdIC2**=V{TNߦ.eD,p4iZyfjhjn|ms_RthEfM^O/Eg%4Kw=֜R}|ЍMW!zkt^/]K\)3['(['CqCh>&2K2`.]UIژ蟚U?Т}%vSsh cb{V~yh--1B|MgB;2>OŽ;WZsV@J<1m3LC>=.u{ 2GmՃHAm"'_G%8 4LhK|oEFyˆ`aFUd%@^#yǬv$|=uH@OU>ҋ/AU)Ay UV *n˳V@ 3ieq=>΄l|b }$R&VHp=Z۱\QX?ibtj:߻7YK-5ot=wzfiZ_B{|O2E0=o6pƓYV^~UM.>?T8IW-ad,gIbġʽP>+p1-Bu-UL;m=DjvϩlW~5 endstream endobj 45 0 obj << /Type /FontDescriptor /FontName /HTMOVS+CMSS10 /Flags 4 /FontBBox [-61 -250 999 759] /Ascent 694 /CapHeight 694 /Descent -194 /ItalicAngle 0 /StemV 78 /XHeight 444 /CharSet (/a/at/b/c/comma/d/h/i/j/m/n/o/period/s/t/u/y/z) /FontFile 44 0 R >> endobj 46 0 obj << /Length1 1510 /Length2 6790 /Length3 0 /Length 7798 /Filter /FlateDecode >> stream xڍxT6"t HwHK400C !Ht+4"] {y}k֚yu׾}{f s.+%XCpqԟxyyyt!(oIC D 1y B|>!1>a1^^?/ߎpg1< bP<.LrpGOg-YG+ '**; v0:a v@VpG ֧;7l# p l:` 2@C k qnp:H a.WxtrVˁp||J'W"w0;8a 4ոNfu#n@htu @QFD2 qDp@8Jg?]@F:3A Ĝg.(̲r a+h+? \m4j4g$r{VN7u$Er֦32X\ e@zIi]wSҲK#5ԅL` +جD>~T BY!-(㰵T1% ݚcNoJCdC='p# Y}{*uns)\t.%Ңs j;(Bei^/T jH [8'}OPP}K~,3ռ,O M 7_d>Ϭ7'.3ɳm8P>42yM狁8ꌷ,;ʍ'ĖCJb !ʦx6H=Zy sψuw>K'f_5T8YBE6Eq"q'g`(M؛P)҂}c(k%Ǔ^e:jg^K EnZKy 5{wL [1"Oj)o>n-+zcy|xaÕ§{uqmAGrZr?z9e#JxGjFcV1kI/G8WPRÊBf= 'Q?JQW"uM+6 >AJCT+4?|Wyf͏o:?S#_3]YW GJM(XEK:@,HR6)^__+6(X^֖{$SB'S'1$2QZeP)5lR𾛎\\1z-6ޔ9g p~=i5tGFJ >}>J僲=+R GmԇCzZ*ުc.8GF5<nǍ[Ѷ1iKg*%=]b{V889`W.u^/>"7K&⻡,]$hv"U/b. nd iȄ D :Iǫz;|ŒPvfwflތہ\~k3ϕZSMA"u0Ja'`{7O5%VJʖZpm{OxXNh cs_LAf1[g"fV搐w< flY[}@3Ե5 _Ro\PGbf J.3u73;B'/ˉMy0F}#&>HMqDSgAݖc S+p5ʃPm\WPZg4H66Y_9qPO- [;R ݑzф34Czm.[.;މ׿H=6G+jIGڨ(N)avlGqȗ3!vbZm$'^.vR6*;\qUۇ8s_XzZpPwS-8 ּ+P{> >k?GVS.Ԧt~֔˖ )cvd?^}\}$ւKH㖊O(HC=_$/s%(4>GKR؟婾!G̅(v4v|qJp0rMeƤpԲnO]R* ZS3b c=u8j~npAcLJ,_k7C)7^ab$ zrǣQ}u@7ŠO5r$nٿ EVf)9~Xn2EMaeABœ}qdq"` k+˾խd;kNYa?YP}ڑc]QF%Mb&ىsx?`XIUe 4TA]obq6"Cvt1~Z#73aQml鮙<ָD˥V¯}Qc|ٷԋs #q=#Ջ%Kk)mV2;6 Lg-; J~ABAߦqj wy˨>!trڢP8xZ3G ҵuiw'{2J׵IveiOD H$M_^z߯1,V<R6fLY%Zӛa|շ+O1hË&^kYzPu9?O|>+Rwnq:~Ēw{\*1Qs淎=Ii.#[RLe%IW'G>Y#*=? j`VqH].4 :$=%3c/'{O=r4\q){e%Ǩ({p62+|wFiFʠX i2v8D4 : scE]Dw{) ꀦ~,.O3vEͻPK;mhd+5ϐC/}kg@;0|%5/ qzKDfi*0/%è}|= e pу/zeZI@o[!d7=pb-Dmǖsehohddݒ]C.n&}:|CE#TUvf$Z ٽƳWԺN]"?eaڟ0 T㼙ğNwb:nCGIs :ރ"RM؟`I;P[\y>fݨ`Z"(ЦevYh=ޑ䨚v>VX=6FL.hRj2^3xD9h6GjJZF`*PA7M=ĮLK[+.gUqc'E-#骿$\j@|dfaUK\d,x+nԴᓛor~Rςyky%w0P9TBx)*'%{VЏi.sF  O ׇ9Ge)K͋QrY#aOaoK]V؜MͯPwZe#Copfkj%RF8qU<1"Ȁk<{%hxﭸ/IK 8N"4lEZp/E=;19U)iӀa9ɑxg}"ܷ3;n n)%PsV- 1D\s=1qJMa@{kNa7G4%9$=OJj,]f%q2SQ~MDZr(םhlb`eZňx8 x8 }6؊ҍ"5 (';ܑ|7>SO륰|N _t!Ml˘ysqY3xd]ahͫ*x{wy2߮Zrho׃r ~Xxv4n^ɫѴtuOExN]ųEL*A+97iVˍSu}E!^1{-~QI4fU_ӹ"-߮"ׇa(a;o1ɔZmjI=\P؞dgc&L$*1sbܧsGmK Eq\}e(m9KsD撻z B,JSpUk3Ľ|"R7^uE rblkOQ:kVqGC֠Yl{%i߿[ a:&exɥʡ׾txY4krjg->wЄ*dgcTH i@qtEPk|0=F1(QŮVbxF <\I]_x?zkŐ\QhɱN3tZEtCA*&&ŻplViwS=>ﻴf2nU5>P7smY_~d"qx4#:j~Z0JG?T;A⾧dIĦub͎v]5zAi'O=e-ˡm'?bqY/vnn3d_ K. m%(4~B6ݬZ9sOSӀ>]v?ֹDzl hɅxk]紉w{e8=`淯R3C}L4l_xuVV07bg(8k]J_Pb=k-y2B Is(Q] Ӎ`%`ݏXDQ~T%&X#:W3Р"z-"f'3wk ּ6]G7Ri|477N'/LbDpqdSF6ʓ#u3k tn#IKj\Wl(jЦ+bu<`wFU䮝g0gkrždB5[mYRqo'͕nr$]_Pswf&zf)*^!{%j(S`6>/fJѰiY 7NHe[uGX55howrS7yz-d/#9Pb_`"vyٴC 7~%Ww"l&MQboF S.O Fח HOzZu6H pRh%V^tA73h-&j?vւb9w\HBⓤKH8^*8=6=3`,=tzJXٜ^iѢmqSl@oX}3i-*;8EF41T><5#\.0"c:=0MmshTJØn%6~P_]aj}r=KWNKu.%<+QIKv#j 4d엔+x_:KϹ{("s2jzm*T6[N.>!ZbH3_ˆ^1wOgu{P&dv<ϒ֏P@e#}g"b:Xq"yjAP>S} LAxzH'h=/O=J<84Q_DNnj.Im4 νkg3DV!qgBL&m@̠EAM0~Ho,8lwkr z:S9$ 5+>1BL]?/=8EQ:Du*,eƏlG//ib}P^6JiN.mgO.;Ȭ43mHd$oT0~WTnɱyӕjJ2-ה?g5ޫ1.@ۏ_:hbKTfDO>3Ɨ&+OS#a(/5jT1qU$mL3cG*^ `ޅJw>\h[.Om5$ !O]7KVzs="k,LZ mv{o~ VB`_тR%4G%)W)h5FT^RC%MܭBݷZx<:hڸi Y):ce endstream endobj 47 0 obj << /Type /FontDescriptor /FontName /MMTFBW+CMSY10 /Flags 4 /FontBBox [-29 -960 1116 775] /Ascent 750 /CapHeight 683 /Descent -194 /ItalicAngle -14 /StemV 40 /XHeight 431 /CharSet (/arrowright/bar/braceleft/braceright/bullet/multiply) /FontFile 46 0 R >> endobj 48 0 obj << /Length1 1626 /Length2 6036 /Length3 0 /Length 6860 /Filter /FlateDecode >> stream xڭWgTݖ7Azot$HU U)&ґ"(D&(A@u{g}3gZ>gq#\!8FTRLB`vE!D !`(k%@A( @0@HI$d ZY ;6 u!0`!ωAa@PCP@AA8"pCn -źh$ŦAAo AyCh7p v | `n?(6 4Ebت&x1kX7ᆍ#@[` ` ߵ\!0`H _4/" h4{:_"?و?QŠ!0712I)lM[ '+zp7@R/;}π`- , E?qwhm_]wpo)@o(,K{5/>= ;pw,bhm?lŀ<n@v^p0!X] *)!7-_.wX7547#?<MK@bg+?a QI;QiIx?$!}KH?:9 F B9c7ퟆn/ v󟝇@! IH13=+SØYӾM'YRgEh5$=zI:R~P)`b ykcL9G n ;Pd|H`''aS;iP(tGțAi[qhj ?8>n'l_aM S2>LJ@Wgo#q,|v3bILCW B;_و}:mj}Ti[1ZCk.12I olx teM`{m&('ALb_l˰ZׇF*q훸 wl3>Rٔ+s>Ѫ74 p/_~6PpGt|"Ťd>\?q^|Sޘ$ cNe~yns^)pz=Ymh6B*P\KAa%>' CB-Ѱ|C;cC.W(aoZzNZ&ǸX;[sCc$MjEx@r{nX kzc {vXZc\A}G/016A-$aQ3 Bb:lzQ7mW#Dϱ#m& +=gQCGsbw^cs[I;ୟUfG8WvOn_|٭:ks?V[GqdZ'⃺CӆR,ǟ2Wc\.DuT~~IӝD!;~ɞʪjPdz,j\dIC g#s%4\|!;V<^5V'ŗI.$<0%e*mӡ{6iJ]gLVjU 4*𸻿ikJ$Y'&ձdhfBGbBfu'+ԾWSrܱ)͵5Hޙtp>iae?zM>3CF>'ۑ'_Y.tvZ՞sj[eD%2q(_ >aG0[#Ba;U73/wUoL=]%J$r]h-P)bl{۹q{f3N 5YHcIen(c`Ȼ`R\/W2vḱc47"Ըm2DL5>r I %~Ͼ G5$/5c J(ȾB^/lg,\vK:dWS>.<|ةl]<珳9uQ}c(9V[PMnHq~ϑmz.cz{F_d,$MTs;.m)+1(X9pKxBQTmE̵ s`uye ᧣plՇ, 92jiSʴ$*Ž0 K:[s݌oƇU۽ S $g[M N@Ol߀suhWh/f[q˼呯5DnqB}VɛMFb}۠k@|}g"GZQʦӠd4{5Dޗ13w:cLfYzyEПo6<{ylaW',CBh6{Bt%>~ nM&MX\-bƃl<)|fƈ=u3ck0X:.V83b<['U隘F);^Hz+ eU(i>ڞ/2J*k!Cjv-H$q %mOnGhuutG|%8CgCE/=w<%Xq<>cG(r۬n4Jl !b7U$`f0,+׉%1<(^",FfCqwUs!-ڮR 77Tn L< o &V1mƻ fwiJʜXKRC?>2&gZjfb CON4M!'HLkwiy#GA|QӇK|syXtN6! T$ޑk-Ɂ1?T#[$?OJ9mNK#҄Y-(dS||uWosXstFxi1n [StND>P.tiOuya['7=W|3Sz%uHm&[Uw8hu80(k \s_ݪkᙪUww+6]mwJs߿(SF"㛸6l2NUvF9BK^EV Hu2F_01~09^]6C"b]:9 X%u #xV_T5U]%ʇH.Ke?ur8\n:tLLQO![#㍏hh1{Qcݾ},>('>yp6Q@Kϭh ~&?ȭ^ѰoP:̩Imo@|T ]6qK r] I@m^q}I˰^i.|#jNVTj ?Yia:ɚ7mP[ݟ,0穑7œ[Q _9/zHGݣY@rN7 2R7%_q,=z<N3,M#xK4BC$~~YvEB{sO T-dY?X!8U(kOA11onIdg>/!^XYiX mP6B}@ۻBmh%F{o3M34lYtC6ᴟe,>c#3Uڙ\]Tk:2 . iF߻v +W{gww:<?{Y\s4Q^u%9ksrA8;)Tu8V>LMw_\3ȚنRBVJ0.3op{{%tțF;p5Ĥ 8F.h_ƄFI/[~gNJG5 uLҲ7y MǍS)(u0~{c_n`ײ :0Zuf93L bY7S;Z>Z9k>oh z*#ndۗ7"tlٺ_Q[z(FMWB`|Q_j$i\7 c S(}TP%-GlΌnq- AuCZi"t^1'X^轌q77Ef i 6 -va7 նUUwlVsiEcS0/'/҂w'M 띞dx9'6rRI&TD> endobj 50 0 obj << /Length1 1630 /Length2 17171 /Length3 0 /Length 18015 /Filter /FlateDecode >> stream xڬctf]&;Iݩضm۶m۩ضmfŶӧt{&k71"55= @QZƖ]ZW CL,`dnk#dP560A[;wsS3'*9%%J1毧 _kGEcc1 (+..# Q;[ R6[տC[#Js8u3v34GE3v6wt0w:8큓-Ml_ 뿺`rNvNQD?&-l )_0N6'c7b f`4mL3*_t?/Y_V3s'Gc+z1 65gVmLlt9_ "gf&odkc026u@2 B -le%owc HY4V?}ks+V5w0q'm1K  ݿ"nFrNf}=\/j+ -m!*cZ_? ?fP 8)T#mk?غji9ܗ>$(G{ѭH{p 6I:X)huJU<4XTw't? ;]P| Sc:k INHFznp(c91| \X+2[T]=pޗ=Ч]2~-J^0PQЩ\?_esQÑjc{,JXȼB'6HL 1M^]%{X!o-a!fYb Q֔TZ ١iR!l k+1) XẑfIBl⒱SF߅>Weאf1gVh`9jX 4%$Sd$)?ak,E)ES%jDX6+V_$%TlhRV稂hJ/$hfPCqYR <HS7({O(WXkS,unի 9:n])O%4υ|>"b_ O8vT=NQ^YjƢL Bzo z9'=цªx'cdԥsor%GwA(jV/G6jt QbʸeJ)0=+- 3m*YY8o6?b\E3qz7μAA-pfW#gk)R}Y3uFÅ,9H{ g~$ܽV00  entiR?<_Mߥ>{l9 W"۸0}Oð2k,_t')+z \ǒi4i;P vwOV), #{U=oa24"ʦO`f~ALb+Iw^?E=s4?Qї OxAsтtީ9 #kQC}F$l yRqaѨMvv8ԕ6RU.z67)S,JP sMi{M\*vSƘϹ)H.,X _1u_]-|@H H/tMn߽"#M+%^qzgɞSˤSdC*N!nx=ڲ _wmw/*dr. j메Ff2>\sهVZT iC+ Eŵ 6`Xus!U1 B]Wra_[$ತ+)Y Ώ.xwGswO 15ZNG\ʮYAM=tpLKz$c =ea4UrѺ:uZI95G=vճ+RaYgpv 2w썠ڊx0oW&>=4x*űD|PNq 2Mo=Vb`$5s^?T  <bE]K& Yޏlyd P5 }M/PLV D{&(A-fK@ݺ4U_A0r (9vi}[Faӟb[|0u#ƅE07 ?9ҥ;rerc7dP4Iu>S,.s 'R$~9db?\}dAHѭx$/eR>R9uerI[C>Lc;/ NLh+(j="[{[j{0 ᱕u&@ĻH̖+j|?tC.c"`%IHSG (MR"z )@R qIvj@(%e-UX%"TۼQʮD69ԯc4ה"9f BoŔRJYwA-##/x5?F`q9 &GlHd z;{cT6+*@ܜֶ*cJQxZ Hn1ͧHUU ֢*wc&{V`Z4L@Dlᄔp͏2gKUMi7t[|Җ0$K,EmPӳ IZΚ/5{5W0=껳ts׃ /eh-S Ѹ r;MԚu"0KMY|,y.(k#<9$ܘy*yB:P}M%]n֒|} k q@O\QKP| `wH'3:|{p.; y|ui(•K_&l45u))Z\_ 3 #K0q11'$"bbIe]w>̘ZgFK"'jQT>/DIlՓLZaasJ)8̢{ḳgP$,t&ulJmxڴi"6/2g?J\/ 4_)F# +|pa7/':9ՁQm0\; B$x_}t8' Oj |=|M=?y)aeсp͋ cPj&Hc}a& ^!**Q*z͎]OILe!p&c15f[IV֝zs`p&jŤ]En8!"u((pYB1PoEUbud\Y#Ň)NL7ς@ _V6I]^v+{x:kh/ efA _d+2ΫsFE*-O;j9lORu %N ހ5nSzh9U1=!pZ@ NcLvi  V2'\C'=z_sCvL^ $!z%I$46FKez,Lkx_ۗna{kxiq-̵Ϯ`  Q~`̈\US|y :2@\a?)\d%)s$%KAU!ZnSS';<Ř.p^L764v[/h,4{##kL`@k/|ç*(V*gnP,c.`KO+l+!j%Ete;VD竪7u oY2PQG/_@ďg z3FL)O<.I!a vXx2u"z>\yawvوrtXQ?!v4`&U4 e6Q̹?}/ 1RE9훚b"c40­AZ<: \!vhj{sWB4&q!5֩jk_u݂S'\G qFB)>1 R9_|Lï ׋/jEy!Oψ 8Nk-gjb]ĒȑCvSy1M6maxCh)=_8 ` 7& kg *E=D3PW@ XhGКxz'w9n=dP{7:*4B Pc}-aI!pI?Iʅ^F:[Wi<"/^4sB֕2/]aH$Bx;SLIS="`94 Tuͼ H6nfVgFPv)(gIs;1ᖏJ>UTD=_:19ȠV&0n#O\^M RNal5"2UG5ڭ$ +4wbKPp|bpw2̜?rR{9gWD>/ O)x>Tۻ)R*>iZ:XIx-0|Rv1fȿyGHsRI%=BȁXRɻnXԙRq4vAD.&Hh[n0OVZZ!>ވ6ӗb&ߌDd9̦:T/IDilI/n>k@Ԁ'䴝ԔGmԏ\3ckE< E?^},KHJLX+!( έ~AP褯VWfԂlҥ R? E!K4QNo6[|&ol 4|ӚbbctZpn'p(GI0]}J/if\ben~bE&tWj"r:E:7Gho֋+PhW:ئ\dw@qE@5|*7/ tp5GI嗘3Y"C])󝅷n*XS&jGI9cݮ:3SRa7cz)ΰ9a $YjVi dq @Z?/Ï/]s᠃/.mymu6 FPo_/F]$Gmx3᧘vM7( "W9ŗ̱D8Xi6dYL!z9KJ+:'A/r҃j :NL"5Eꛈ/iOv:ӴK=՜4wUNQxdy-Hh1]HG2R J:#YbAҒ~>ԀЎ@B_CciB z#򘗪uЗ-Mg+c,;duM} !%d=-ٛlDmv`#PZ038aIx?3]3HF=gfcEa^9$PZˁQ_x'(=GX^~rCeWڻEXBlF -gk8T@˗ŚP.8 &z8|Ħ˃I2@`$h{7Ly'q Qgچ yvӺxc#G0@ԉs&{mbG4P}eBPk/ujJ-Ł9}?&O)ǔ5oTa)dxI@߻KYg770 +J{&Ь'3 GUa"n<#|Mfy") rzL i`L.=:MyK11{H i;⽶ֺklGc?W¶}rF`ݟ_^भBm3<8Cb`DLuVq=iY2cJ!T]JJ :8,G@̠m (l2Fi!H(9ɐKAçUmPc-!Jpz0QZ. NFi jL7&A}6֠ *DWf^ 㠕(]q/"~-#oxECtzuㄇ1q18ltStK/E oj^w~jZduga85z 'VBLc 0} ]NLU:Y7c^ጊhAwrT䀇̒\=%w.;<\k{`.hW\a FL`:䡟NGavʁ553`ܯBυj(mpFv 8xzӒO3Te .d^ɬhyʌ)ƹ)yk XD`)w:`u*˵R1$')_x⮴ήFU0ԋ9po6Fhs}M_hA$駷<[0-1.H]ׁN46U5(C=~6宒xRVAusJztcBccXhP%I?*_Y;@nۖJb2oC%tw!RnI4T$8`mpF;Q[.Vbc;<2" ;tȐ״{aGK~BB.96Nƫ'p(Wen LӢšLGhԦ4.iyg"4,Z$`JGh>gJ~63ru$@8r o-0=w~A7.%2/ e MD .yZtm pԒfpYɈ9R86$/Xi#xOwjcsR1_'u͌cɛxӽ$|ːc\o+z},;XX|]Ɖ!dX8ώބ~~]W/BQR7\-u#IK%2dg_GDƶoa*j+6O!ƤGxƃȲ`Dŏ(gcy^5Xioɘ6Sb*qXx8pk\1O/6B)w}-yr)+`Il4~@j) 0JA衺L}խ8k7bwc0(kя ;6wը'U@fu\]^ߴC?_9vwdH|>0_YT u^Jo}v}ZS <%e9E8X>+W{5iA^MҮ[F$i1k4DhD~ ppC>ȱK=t,]'6k(3U@RDݧDʀ|y61zlc:X٣hq#{e$Y' Uk֯ -;QcJUӍq|ORψL@5YNU50?Ep+p5_ =ϕby=&Zܖ2,/c%ںHӴYk홣fxa?Z[L#ݜ9AtPXH(JR9:R1,g )VO*yaSE؞KUqC*kɴ籪U*M68%sF/O lѓOgy@,M܇o<-*bgPKH*yVM}7a/WT@K y'yϺigeqnߎK3\$`p~kxJCXUw$V m5̲S~:'G1Jj7V1]NZ&?zHE\fn(\Yzԝ8#OVmFAjТ~>@ "7vb:SSP@F!{Lv9L9Q~\YDu{nX9S"xb90{[4jp_vCUQ%o^\EI% =p]z,E%D[F6_M_k~HưXcs v_S).U:^r@s^FS.98.,eǍ߮NI&Ro$z[s#VH#6(G C<|ED?.ŝk׉l4Nye}n*uْӜfvU;r:yާGUTs+pzpbA9qd"Da5_ kW#oޥo]h#*Q,co?7I"8qH`Q֦d{NjɮpS,r䴸L.64ݜX(bFmA<96*EcgaX,b${]UOQq<|­KќTYf{iAZF:Av9YyGz. gpfWYxhaA.&aZmZ{OH?>p=DI&4ʊG`l`NK :M=3cY{Gs.VXQs6ztLP3?W+]PK*G0sQcAS/q`=.DO6f ݼ?S}ԫ@$o(v= &1!Ķ wh`׆yIs~<ג-{ mG0}*Wө:5JG䒣ZC< t(C(y>^lX?5?w^zWಳ#BEmK4j|'3X٠*AV3CH;7H࢟rr H5_uXU=:*3d*^[86|71yG0ڻ}Rm[UC}?X#GwaV>kx(2V{9䤳y%j}&8-x'~@e@|}&}-f&c4e+_`@/=;="(~$ D~!ۂoV@ G}QM_b[6i#i<8< .3a:ߒsJJuB8g1|[ۍ wK4M_,&ox:$R4~7^p.R'f$@!dR/ZK hb8iucJyk5v9Ik!냀&v8Hj$2I0A`~y:n o^m aG)}=+g,;Rs;Ă^ }ۇtdu-/]\ܣL7A"w-"'\:f4@^F*5XqyʅdF6@Şk@7wVX^\ Ǝz:).v3=*_.e_nt)\0xe⒛T;јN%VLFsWˊ*etQ{=YH)7Z;UC(3V i:ryO3;wE[IӀ\өi7OWl5Xq*fQdΦ/4Tp }ŰhUj< 2}aUٺ.QP4.W#f^PFЪ4g)&+hJč`*" :qciv>jVnAEQ0.غT"?:~gC)fZGX~RoN; qjhy"Wa##(H_V VW=:z0tԖ=p(Z:ѢIn-8.yScׅ^0H15J+y׻MyNπw:_`&ɴP%pwh2\jX2nc}aI`wse8oqB4(#埵H={{S#D'SmeA3FrXW|] ן}nǫ a[N?]W!\%}i%p+ 8o;yVJ<.BC@S_|_l66UF'>iNS,@NeEςThM)9Yxdt0J늆fg6 uǎaal,O}75%N>c4(Y>~`Y@ܑoVDB=c.˺ B[s߀ЏeE^H0L7՝YF WcH(`S+/OJ"`هH}~cVAS =Fך *0кҊ(`;t&'Niȕ_4Y<euZ$#+8b>&4BTO8G^󜌭&V ld(] xƢG'_Ԡҙw4uGyZi,M|+W4 9 $oě1T F֘&妬OQ9=&n+1`&ik~!+XHs)G`' ~ @/~IX._1FwvQN,4+]_-LZ"A>g:;OtK YatG)eF 0*rlӌT\^u(u̬A+FTnVցAGA. ߉L};Mxe;ҐnA6?H]p7櫄 n"P'^s"{dXoύeO\λ4>Y*,?}t1nC|Ls">;|QB,m6*TA_skXX2k>-[*w6}T؆T(LxMVĭݰ$8wFvEFqׯ`S@㥒{0!/W/ BvKwWYr'COCvAǣWw3zdBw9ƫS2 FkF/AK[80t?Rܮ8${ nVnDI'f^2C4!=V,vA6x9cRgId h $k|0+z|$Nm?nw&$U 8gsw,(?j+[>5Rz{n4mݑF•ӡ'&uG"5&MHÄ /ˣG(=3WJɪ'T 7k=`2'ÓMݸٯ||_;EeqBH*e |Aw=:֚1";P?УqZ{0bbycNbLHU1r=Wr)~h뻭_r-]?SVs9ޱsٿ6a>oΗDzks޼@j{,\TCAx:6)׍p; b\Vm 2zQCAGp}s=4\T&$Փ1 6 S}]AZP.eM Qfǂ>_o$/J\fWeF-i EWtGVy ^ޫi+CE6d>a/F!D&!W|kdn*Ox G e~ǰF32aĦZk"~\(pR:gv{*B=mmvY .cH,₶Y{┼@?/fY98S!٦nod;e?'cb 8iL4%aoÕ x?P\?:_RbRTm\Q!I>GsyT,|T9 ɣ|w/4zF;\D!djը,o6]& L]-۪ _@{<,Tx4ѳ,b~ V!]Afi%56穧ؚMvѡ O8Я\Uk<:gabD( Yf([˝֝j1#W~MQɅ|EѽHwg:\i,GR+\$Gzg bPPic6q^t'0ү*7q? Xy-ͱ#8Hi6;ey!{hMDx1wŀsaQeJ}oJLׁE"eut;9w0o<)YBVUuk.X{Z=I;/(`hW,Cp^yУR5K$( \%a:[q$rn*WhaHAPӨ"Y-.d˕OjyH[DH&{oQ-L@ !<'7!#BM'_z1ʜJt`lQHm8"x?/~_XsZ5RB:^}8̓wHLy^Ss!'' _c_ߢ-ѥeLSκ2q>|ҼZ.0zsCTVqx |&S[viAC8VJBԕWaWp=قkx +P霯o)t>6N[_DI,A_[ %!iA[]+hWjdVj*dˮVRgJADL i?*IL~}2AyIID50.z&\Qլ&ce~)/t(/|Q&Э@FV7Skab/ 3FHN<ɬDD> $ -ai{قA딝~(ƒZ\2j odvI*@w)]qXꢳ/^5\!L7%߭t|_>'1>s*cZvғZ2r!1~I&ß)ŬɄ@*Ǐqp4iASCοSJH"z%ZD!3P5, n9 LUۚaX>ad;qچ3,rΓvA]l>JRE|&O$%bFZPɶݿe{lfŔn^B/]2;(12^UZ]c4JE&-U=DUd:W^o.͘+ c sۯ{m\6tSU|$ᔄ 2^|CZ%ڪ3N 8X{ܥ+ $aנfVݳ"4:U h>npαʚ|T>;$5}A~,@CM*{g7S*QvUV4şx,!9ؔdԿv6GB-]=&.PCbJ+{t,*>2 [ٿE̘M[t{,_j' gvVܴtո:?]Hds endstream endobj 51 0 obj << /Type /FontDescriptor /FontName /UMWYCA+NimbusRomNo9L-Regu /Flags 4 /FontBBox [-168 -281 1000 924] /Ascent 678 /CapHeight 651 /Descent -216 /ItalicAngle 0 /StemV 85 /XHeight 450 /CharSet (/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/R/S/T/U/V/W/Y/a/b/bracketleft/bracketright/c/colon/comma/d/e/eight/exclam/f/fi/five/fl/four/g/h/hyphen/i/j/k/l/m/n/nine/o/one/p/parenleft/parenright/period/q/quotedblright/quoteright/r/s/semicolon/six/slash/t/three/two/u/v/w/x/y/z/zero) /FontFile 50 0 R >> endobj 52 0 obj << /Length1 1647 /Length2 1691 /Length3 0 /Length 2488 /Filter /FlateDecode >> stream xڭT}j5IvjCJFUmn\\:\dE E0-|X@3݆E1ba0PYB .B`g(ΚHpd!Ke- ~@HG C|pA@̃纀^?xA$ěy0a% pP +Z\""ā0H h@B>,H! ;P#(GʂBN0G 0g &ĢX"aF\#VpX0"$"%sd!,C,ȏ,B(% OYOF^c"aIsr0v~|Ǽ FsWtjnq 7r:N"[ 5$6U6֪i)f`kI VfFsuzG V;Roy"]οFm/2+aDnK3Mחj9ZFUͮ/(UIWHA5)]1M#6N̰V >1r]=UnyinN 5fj\>#Yk8r#1kla0ضbM172OUB 4|_[I6~Mݝ|'[ xožh)[iΊ/Dh8V9"grѐwvd?㻮_' xQLFRD/,> endobj 38 0 obj << /Type /Encoding /Differences [2/fi/fl 33/exclam 39/quoteright/parenleft/parenright 44/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon 65/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P 82/R/S/T/U/V/W 89/Y 91/bracketleft 93/bracketright 97/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z 148/quotedblright] >> endobj 6 0 obj << /Type /Font /Subtype /Type1 /BaseFont /HTMOVS+CMSS10 /FontDescriptor 45 0 R /FirstChar 44 /LastChar 122 /Widths 41 0 R >> endobj 7 0 obj << /Type /Font /Subtype /Type1 /BaseFont /MMTFBW+CMSY10 /FontDescriptor 47 0 R /FirstChar 2 /LastChar 106 /Widths 40 0 R >> endobj 4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /QMSQAG+NimbusRomNo9L-Medi /FontDescriptor 49 0 R /FirstChar 66 /LastChar 116 /Widths 43 0 R /Encoding 38 0 R >> endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /UMWYCA+NimbusRomNo9L-Regu /FontDescriptor 51 0 R /FirstChar 2 /LastChar 148 /Widths 42 0 R /Encoding 38 0 R >> endobj 8 0 obj << /Type /Font /Subtype /Type1 /BaseFont /XCOFEZ+NimbusRomNo9L-ReguItal /FontDescriptor 53 0 R /FirstChar 47 /LastChar 55 /Widths 39 0 R /Encoding 38 0 R >> endobj 9 0 obj << /Type /Pages /Count 6 /Kids [2 0 R 15 0 R 23 0 R 29 0 R 33 0 R 36 0 R] >> endobj 54 0 obj << /Type /Catalog /Pages 9 0 R >> endobj 55 0 obj << /Producer (pdfTeX-1.40.10) /Creator (TeX) /CreationDate (D:20100427141334-04'00') /ModDate (D:20100427141334-04'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.1415926-1.40.10-2.2 (TeX Live 2009) kpathsea version 5.0.0) >> endobj xref 0 56 0000000000 65535 f 0000002125 00000 n 0000002021 00000 n 0000000015 00000 n 0000322391 00000 n 0000322560 00000 n 0000322112 00000 n 0000322252 00000 n 0000322728 00000 n 0000322900 00000 n 0000003523 00000 n 0000016692 00000 n 0000016762 00000 n 0000046960 00000 n 0000079355 00000 n 0000003402 00000 n 0000002237 00000 n 0000012263 00000 n 0000045600 00000 n 0000078748 00000 n 0000080735 00000 n 0000137809 00000 n 0000210868 00000 n 0000080614 00000 n 0000079494 00000 n 0000135840 00000 n 0000200568 00000 n 0000213228 00000 n 0000268897 00000 n 0000213107 00000 n 0000210995 00000 n 0000259414 00000 n 0000271135 00000 n 0000271028 00000 n 0000269023 00000 n 0000273474 00000 n 0000273367 00000 n 0000271226 00000 n 0000321752 00000 n 0000273565 00000 n 0000273619 00000 n 0000274224 00000 n 0000274684 00000 n 0000275246 00000 n 0000275469 00000 n 0000284577 00000 n 0000284838 00000 n 0000292755 00000 n 0000293025 00000 n 0000300004 00000 n 0000300248 00000 n 0000318383 00000 n 0000318880 00000 n 0000321487 00000 n 0000322992 00000 n 0000323042 00000 n trailer << /Size 56 /Root 54 0 R /Info 55 0 R /ID [<4EEA612D2DCE15C378DDB5CE211B78C1> <4EEA612D2DCE15C378DDB5CE211B78C1>] >> startxref 323301 %%EOF bitcask-2.1.0/priv/0000755000232200023220000000000013655023466014472 5ustar debalancedebalancebitcask-2.1.0/priv/scenario/0000755000232200023220000000000013655023466016275 5ustar debalancedebalancebitcask-2.1.0/priv/scenario/trigger_commonpaths.erl0000644000232200023220000002664013655023466023064 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% fault injection primitives %% %% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(trigger_commonpaths). -export([config/0]). -include("faulterl.hrl"). config() -> DoOnlyOnceHack = " #ifndef TRIGGER_COMMONPATHS #define TRIGGER_COMMONPATHS static char *interesting_strings[] = { /* \"generic.qc\", /* Used by generic_qc_fsm.erl */ /* Interesting bitcask things */ \"bitcask.data\", \"bitcask.hint\", NULL }; static int flock_op_array[] = { LOCK_SH, LOCK_EX, 0 }; static int fcntl_cmd_array[] = { F_SETLK, F_SETLKW, 0 }; static int open_write_op_array[] = { O_WRONLY, O_RDWR, 0 }; #endif ", %% MEMO: PathC_ means "path checking via strstr" PathC_CHeaders = ["", ""], PathC_TStruct = " typedef struct { char *i_name; /* Must be present */ }", PathC_TNewInstanceArgs = "char *strstr_1st_arg", PathC_TNewInstanceBody = " ", %% NOTE: 'path' below matches last arg to config() PathC_TBody = " int i; res = 0; for (i = 0; interesting_strings[i] != NULL; i++) { if (strstr(path, interesting_strings[i]) != NULL) { res++; break; } } ", %% Memo: BitC_ means "bit checking", e.g. open(2) flags, flock(2) operation BitC_CHeaders = [""], BitC_TStruct = " typedef struct { char *i_name; /* Must be present */ int array[11]; /* Array of bit patterns to check, 0 signals end of array */ }", BitC_TNewInstanceArgs = "int *array", BitC_TNewInstanceBody = " int i; for (i = 0; array[i] != 0 && i < sizeof(a->array); i++) { a->array[i] = array[i]; } a->array[i] = 0; ", %% NOTE: 'path' below matches last arg to config() BitC_TBody = " int i; res = 0; for (i = 0; a->array[i] != 0; i++) { if (a->array[i] & checked_operation) { res++; break; } } ", %% Memo: IntArgC_ means "integer arg checking" IntArgC_CHeaders = [""], IntArgC_TStruct = " typedef struct { char *i_name; /* Must be present */ int array[11]; /* Array of ints to check, 0 signals end of array */ }", IntArgC_TNewInstanceArgs = "int *array", IntArgC_TNewInstanceBody = " int i; for (i = 0; array[i] != 0 && i < sizeof(a->array); i++) { a->array[i] = array[i]; } a->array[i] = 0; ", %% NOTE: 'path' below matches last arg to config() IntArgC_TBody = " int i; res = 0; for (i = 0; a->array[i] != 0; i++) { if (a->array[i] == cmd) { res++; break; } } ", [trigger_args_of_intercept:config( PathC_CHeaders, [DoOnlyOnceHack], [], true, true, false, PathC_TStruct, "", PathC_TNewInstanceArgs, PathC_TNewInstanceBody, InterceptName, PathC_TBody, InterceptName, "path") || InterceptName <- ["access", "stat", "open", "unlink", "unlinkat", "rename"]] ++ [trigger_args_of_intercept:config( BitC_CHeaders, [], [], true, true, false, BitC_TStruct, "", BitC_TNewInstanceArgs, BitC_TNewInstanceBody, InterceptName, BitC_TBody, InterceptName, "bits") || InterceptName <- ["flock"]] ++ [trigger_args_of_intercept:config( IntArgC_CHeaders, [], [], true, true, false, IntArgC_TStruct, "", IntArgC_TNewInstanceArgs, IntArgC_TNewInstanceBody, InterceptName, IntArgC_TBody, InterceptName, "cmd") || InterceptName <- ["fcntl"]] ++ [trigger_random:config()] ++ [ %% A quick glance at the code suggests that the LevelDB code never %% checks the return status of a variable. {shrug} %% HRM, seems like none of the EUnit tests trigger calls to access() %% by the leveldb code. There are plenty of calls by the VM code %% loader and by the eqc framework, though. #fi{ % both?/OS X version name = "access", type = intercept, intercept_args = "const char *path, int amode", intercept_args_call = "path, amode", c_headers = [""], intercept_errno = "EFAULT", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_access_path", "\"-unused-arg-\""}, {"random", "percent_7", "7"}] }, #fi{ % both?/OS X version name = "stat", type = intercept, intercept_args = "const char *path, struct stat *buf", intercept_args_call = "path, buf", c_headers = [""], intercept_errno = "EIO", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_stat_path", "\"-unused-arg-\""}, {"random", "percent_7", "4"}] }, #fi{ % both?/OS X version name = "flock", type = intercept, intercept_args = "int fd, int checked_operation", intercept_args_call = "fd, checked_operation", c_headers = [""], intercept_errno = "EDEADLK", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_flock_bits", "flock_op_array"}, {"random", "", "7"}] }, #fi{ % both?/OS X version name = "fcntl", type = intercept, intercept_args = "int filedes, int cmd, ...", intercept_args_call = "filedes, cmd, arg", intercept_body_setup = " int arg; va_list ap;\n\n" ++ " va_start(ap, cmd); " ++ "arg = va_arg(ap, int); va_end(ap);\n", c_headers = [""], intercept_errno = "EDEADLK", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_fcntl_cmd", "fcntl_cmd_array"}, {"random", "", "7"}] }, #fi{ % both?/OS X version name = "open", type = intercept, intercept_args = "const char *path, int checked_operation, ...", intercept_args_call = "path, checked_operation, mode", intercept_body_setup = " int mode; va_list ap;\n\n" ++ " va_start(ap, checked_operation); " ++ "mode = va_arg(ap, int); va_end(ap);\n", c_headers = ["", ""], intercept_errno = "ENFILE", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_open_path", "\"-unused-arg-\""}, {"random", "", "7"}] }, #fi{ % OS X version name = "opendir$INODE64", type = intercept, intercept_args = "const char *path", intercept_args_call = "path", c_headers = [""], %%%% intercept_errno = "EINTR", intercept_errno = "EMFILE", intercept_return_type = "DIR *", intercept_return_value = "NULL", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"random", "", "7"}] }, %% Yup, ftruncate triggers the same bug during open that others do. #fi{ % both?/OS X version name = "ftruncate", type = intercept, intercept_args = "int fd, off_t length", intercept_args_call = "fd, length", c_headers = [""], intercept_errno = "ENOSPC", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"random", "", "0"}] }, %% HRM, pread() is heavily used by EQC via dets, bummer. %% We need more smarts here, somehow. %% If nbyte > 9000, then we seem to avoid most/all of dets 8KB %% ?CHUNK_SIZE ops? #fi{ % both?/OS X version name = "pread", type = intercept, intercept_args = "int fd, void *buf, size_t nbyte, off_t offset", intercept_args_call = "fd, buf, nbyte, offset", c_headers = [""], intercept_errno = "EIO", intercept_return_type = "ssize_t", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"random", "", "0"}] }, #fi{ % OS X version name = "unlink", type = intercept, intercept_args = "const char *path", intercept_args_call = "path", c_headers = [""], intercept_errno = "ENOSPC", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_unlink_path", "\"-unused-arg-\""}, {"random", "percent_7", "7"}] }, #fi{ name = "unlinkat", % Linux 3.2 version type = intercept, intercept_args = "int __fd, __const char *path, int __flag", intercept_args_call = "__fd, path, __flag", c_headers = [""], intercept_errno = "ENOSPC", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_unlinkat_path", "\"-unused-arg-\""}, {"random", "percent_7", "7"}] }, #fi{ % OS X version name = "rename", type = intercept, %% We'll re-use the same strstr trigger using the arg named 'path' %% Which means we only check for interesting names in the old path %% and that we ignore the new path. intercept_args = "const char *path, const char *new", intercept_args_call = "path, new", c_headers = [""], intercept_errno = "ENOSPC", intercept_return_type = "int", intercept_return_value = "-1", %% Use 2-tuple version here, have the instance name auto-generated intercept_triggers = [{"i_arg_rename_path", "\"-unused-arg-\""}, {"random", "percent_7", "7"}] } ]. bitcask-2.1.0/priv/scripts/0000755000232200023220000000000013655023466016161 5ustar debalancedebalancebitcask-2.1.0/priv/scripts/downgrade_bitcask.erl0000644000232200023220000001061613655023466022343 0ustar debalancedebalance% Run this script to downgrade Bitcask files from the format % introduced in Riak 2.0 to the format used in Riak 1.4 % Run it by calling escript on it and pointing it to a data % directory after stopping the Riak node. % The script will recursively find all Bitcask files under that % directory and reformat them. % $ escript downgrade_bitcask.erl /my/riak/data/bitcask -module(downgrade_bitcask). -mode(compile). -export([main/1]). -define(HEADER_SIZE, 14). -record(entry, { crc, tstamp, keysz, valsz, key, val}). main([DataDir]) -> downgrade_if_dir(DataDir). maybe_downgrade_file(F) -> is_bitcask_file(F) andalso downgrade_file(F). downgrade_if_dir(Dir) -> case filelib:is_dir(Dir) of true -> downgrade_dir(Dir); false -> ok end. downgrade_dir(Dir) -> {ok, Children0} = file:list_dir(Dir), Children = [filename:join(Dir, Child) || Child <- Children0], case is_bitcask_dir(Dir) of false -> [downgrade_if_dir(Child) || Child <- Children]; true -> [maybe_downgrade_file(Child) || Child <- Children] end. is_bitcask_file(Filename0) -> Filename = filename:basename(Filename0), Match = re:run(Filename, "^\\d+\\.bitcask\\.data$"), nomatch =/= Match. is_bitcask_dir(Dir) -> case filelib:is_dir(Dir) of false -> false; true -> {ok, Files} = file:list_dir(Dir), lists:any(fun is_bitcask_file/1, Files) end. read_entry(F) -> case file:read(F, ?HEADER_SIZE) of {ok, <>} -> case file:read(F, KeySz+ValueSz) of {ok, <>} -> % io:format("K: ~p, V: ~p\n", [Key, Value]), {ok, #entry{crc=CRC, tstamp=Tstamp, keysz=KeySz, valsz=ValueSz, key=Key, val=Value}}; _ -> error end; eof -> eof; _ -> io:format("Error reading entry\n"), error end. downgrade_file(F) -> Dir = filename:dirname(F), NewF = F ++ ".new", HintFile = filename:join(Dir, filename:basename(F, ".data")++".hint"), NewHF = HintFile ++ ".new", io:format("Downgrading file ~s\n", [F]), {ok, Fi} = file:open(F, [read, raw, binary]), {ok, Fo} = file:open(NewF, [write, raw, binary]), {ok, Fh} = file:open(NewHF, [write, raw, binary]), ok = convert_file(Fi, Fo, Fh, 0, 0, fun tx_pre_20/1), ok = file:close(Fi), ok = file:close(Fo), ok = file:close(Fh), HintBak = HintFile ++ ".bak", FBak = F ++ ".bak", ok = file:rename(HintFile, HintBak), ok = file:rename(F, FBak), ok = file:rename(NewF, F), ok = file:rename(NewHF, HintFile), ok = file:delete(HintBak), ok = file:delete(FBak), ok. convert_file(Fi, Fo, Fh, Ofs, Crc, Tx) -> case read_entry(Fi) of {ok, Entry} -> NewEntry = Tx(Entry), Sz = write_entry(Fo, NewEntry), NewCrc = write_hint_entry(Fh, Ofs, Sz, Crc, NewEntry), convert_file(Fi, Fo, Fh, Ofs+Sz, NewCrc, Tx); eof -> write_hint_entry(Fh, 16#ffffFFFFffffFFFF, Crc, 0, #entry{key= <<>>, tstamp=0}), % io:format("Finished reading file\n", []), ok; _ -> io:format(standard_error, "Error reading file\n", []), error end. write_hint_entry(F, Ofs, Sz, Crc, #entry{key=Key, tstamp=Tstamp}) -> KeySz = size(Key), Hint = [<>, Key], ok = file:write(F, Hint), erlang:crc32(Crc, Hint). write_entry(F, #entry {key=Key, val=Value, tstamp=Tstamp}) -> KeySz = size(Key), ValueSz = size(Value), Bytes0 = [<>, <>, <>, Key, Value], Bytes = [<<(erlang:crc32(Bytes0)):32>> | Bytes0], ok = file:write(F, Bytes), iolist_size(Bytes). tx_pre_20(Entry = #entry{key= <<2, BucketSz:16, Bucket:BucketSz/binary, Key/binary>>}) -> OldKey=term_to_binary({Bucket, Key}), % io:format("Converted B/K ~s/~s\n", [Bucket, Key]), tx_pre_20(Entry#entry{key=OldKey, keysz=size(OldKey)}); tx_pre_20(Entry= #entry{val= <<"bitcask_tombstone2", _/binary>>}) -> NewVal = <<"bitcask_tombstone">>, Entry#entry{val=NewVal, valsz=size(NewVal)}; tx_pre_20(Entry) -> Entry. bitcask-2.1.0/priv/bitcask_multi.schema0000644000232200023220000000676713655023466020526 0ustar debalancedebalance%% -*- erlang -*- %%%% bitcask multi %% @see bitcask.data_root {mapping, "multi_backend.$name.bitcask.data_root", "riak_kv.multi_backend", [ hidden, {datatype, directory} ]}. %% @see bitcask.open_timeout {mapping, "multi_backend.$name.bitcask.open_timeout", "riak_kv.multi_backend", [ {default, "4s"}, {datatype, {duration, s}}, hidden ]}. %% @see bitcask.sync.strategy {mapping, "multi_backend.$name.bitcask.sync.strategy", "riak_kv.multi_backend", [ {default, none}, {datatype, {enum, [none, o_sync, interval]}}, hidden ]}. %% @see bitcask.sync.strategy {mapping, "multi_backend.$name.bitcask.sync.interval", "riak_kv.multi_backend", [ {datatype, {duration, s}}, hidden ]}. %% @see bitcask.max_file_size {mapping, "multi_backend.$name.bitcask.max_file_size", "riak_kv.multi_backend", [ {default, "2GB"}, {datatype, bytesize}, hidden ]}. %% @see bitcask.merge.policy {mapping, "multi_backend.$name.bitcask.merge.policy", "riak_kv.multi_backend", [ {default, always}, {datatype, {enum, [always, never, window]}}, hidden ]}. %% @see bitcask.merge.policy {mapping, "multi_backend.$name.bitcask.merge.window.start", "riak_kv.multi_backend", [ {default, 0}, {datatype, integer}, hidden ]}. %% @see bitcask.merge.policy {mapping, "multi_backend.$name.bitcask.merge.window.end", "riak_kv.multi_backend", [ {default, 23}, {datatype, integer}, hidden ]}. %% @see bitcask.merge.triggers.fragmentation {mapping, "multi_backend.$name.bitcask.merge.triggers.fragmentation", "riak_kv.multi_backend", [ {datatype, integer}, hidden, {default, 60}, {validators, ["is_percentage"]} ]}. %% @see bitcask.merge.triggers.dead_bytes {mapping, "multi_backend.$name.bitcask.merge.triggers.dead_bytes", "riak_kv.multi_backend", [ {datatype, bytesize}, hidden, {default, "512MB"} ]}. %% @see bitcask.merge.thresholds.fragmentation {mapping, "multi_backend.$name.bitcask.merge.thresholds.fragmentation", "riak_kv.multi_backend", [ {datatype, integer}, hidden, {default, 40}, {validators, ["is_percentage"]} ]}. %% @see bitcask.merge.thresholds.dead_bytes {mapping, "multi_backend.$name.bitcask.merge.thresholds.dead_bytes", "riak_kv.multi_backend", [ {datatype, bytesize}, hidden, {default, "128MB"} ]}. %% @see bitcask.merge.thresholds.small_file {mapping, "multi_backend.$name.bitcask.merge.thresholds.small_file", "riak_kv.multi_backend", [ {datatype, bytesize}, hidden, {default, "10MB"} ]}. %% @see bitcask.fold.max_age {mapping, "multi_backend.$name.bitcask.fold.max_age", "riak_kv.multi_backend", [ {datatype, [{atom, unlimited}, {duration, ms}]}, hidden, {default, unlimited} ]}. %% @see bitcask.fold.max_age {mapping, "multi_backend.$name.bitcask.fold.max_puts", "riak_kv.multi_backend", [ {datatype, [integer, {atom, unlimited}]}, hidden, {default, 0} ]}. %% @see bitcask.expiry {mapping, "multi_backend.$name.bitcask.expiry", "riak_kv.multi_backend", [ {datatype, [{atom, off}, {duration, s}]}, hidden, {default, off} ]}. %% @see bitcask.hintfile_checksums {mapping, "multi_backend.$name.bitcask.hintfile_checksums", "riak_kv.multi_backend", [ {default, strict}, {datatype, {enum, [strict, allow_missing]}}, hidden ]}. %% @see bitcask.expiry.grace_time {mapping, "multi_backend.$name.bitcask.expiry.grace_time", "riak_kv.multi_backend", [ {datatype, {duration, s}}, hidden, {default, 0} ]}. %% @see bitcask.io_mode {mapping, "multi_backend.$name.bitcask.io_mode", "riak_kv.multi_backend", [ {default, erlang}, {datatype, {enum, [erlang, nif]}}, hidden ]}. bitcask-2.1.0/priv/bitcask.schema0000644000232200023220000002432613655023466017303 0ustar debalancedebalance%% -*- erlang -*- %%%% bitcask %% @doc A path under which bitcask data files will be stored. {mapping, "bitcask.data_root", "bitcask.data_root", [ {default, "$(platform_data_dir)/bitcask"}, {datatype, directory} ]}. %% @doc Specifies the maximum time Bitcask will block on startup while %% attempting to create or open the data directory. You generally need %% not change this value. If for some reason the timeout is exceeded %% on open you'll see a log message of the form: "Failed to start %% bitcask backend: .... " Only then should you consider a longer %% timeout. {mapping, "bitcask.open_timeout", "bitcask.open_timeout", [ {default, "4s"}, {datatype, {duration, s}}, hidden ]}. %% @doc Changes the durability of writes by specifying when to %% synchronize data to disk. The default setting protects against data %% loss in the event of application failure (process death) but leaves %% open a small window wherein data could be lost in the event of %% complete system failure (e.g. hardware, O/S, power). %% %% The default mode, `none`, writes data into operating system buffers %% which which will be written to the disks when those buffers are %% flushed by the operating system. If the system fails (power loss, %% crash, etc.) before before those buffers are flushed to stable %% storage that data is lost. %% %% This is prevented by the setting `o_sync` which forces the %% operating system to flush to stable storage at every write. The %% effect of flushing each write is better durability, however write %% throughput will suffer as each write will have to wait for the %% write to complete. %% %% Available Sync Strategies: %% %% * `none` - (default) Lets the operating system manage syncing %% writes. %% * `o_sync` - Uses the O_SYNC flag which forces syncs on every %% write. %% * `interval` - Riak will force Bitcask to sync every %% `bitcask.sync.interval` seconds. {mapping, "bitcask.sync.strategy", "bitcask.sync_strategy", [ {default, none}, {datatype, {enum, [none, o_sync, interval]}}, hidden ]}. %% @see bitcask.sync.strategy {mapping, "bitcask.sync.interval", "bitcask.sync_strategy", [ {datatype, {duration, s}}, hidden ]}. {translation, "bitcask.sync_strategy", fun(Conf) -> Setting = cuttlefish:conf_get("bitcask.sync.strategy", Conf), case Setting of none -> none; o_sync -> o_sync; interval -> Interval = cuttlefish:conf_get("bitcask.sync.interval", Conf, undefined), {seconds, Interval}; _Default -> none end end}. %% @doc Describes the maximum permitted size for any single data file %% in the Bitcask directory. If a write causes the current file to %% exceed this size threshold then that file is closed, and a new file %% is opened for writes. {mapping, "bitcask.max_file_size", "bitcask.max_file_size", [ {default, "2GB"}, {datatype, bytesize}, hidden ]}. %% @doc Lets you specify when during the day merge operations are %% allowed to be triggered. Valid options are: %% %% * `always` (default) No restrictions %% * `never` Merge will never be attempted %% * `window` Hours during which merging is permitted, where %% `bitcask.merge.window.start` and `bitcask.merge.window.end` are %% integers between 0 and 23. %% %% If merging has a significant impact on performance of your cluster, %% or your cluster has quiet periods in which little storage activity %% occurs, you may want to change this setting from the default. {mapping, "bitcask.merge.policy", "bitcask.merge_window", [ {default, always}, {datatype, {enum, [always, never, window]}}, hidden ]}. %% @see bitcask.merge.policy {mapping, "bitcask.merge.window.start", "bitcask.merge_window", [ {default, 0}, {datatype, integer}, hidden ]}. %% @see bitcask.merge.policy {mapping, "bitcask.merge.window.end", "bitcask.merge_window", [ {default, 23}, {datatype, integer}, hidden ]}. {translation, "bitcask.merge_window", fun(Conf) -> Setting = cuttlefish:conf_get("bitcask.merge.policy", Conf), case Setting of always -> always; never -> never; window -> Start = cuttlefish:conf_get("bitcask.merge.window.start", Conf, undefined), End = cuttlefish:conf_get("bitcask.merge.window.end", Conf, undefined), {Start, End}; _Default -> always end end}. %% @doc Describes what ratio of dead keys to total keys in a file will %% trigger merging. The value of this setting is a percentage %% (0-100). For example, if a data file contains 6 dead keys and 4 %% live keys, then merge will be triggered at the default %% setting. Increasing this value will cause merging to occur less %% often, whereas decreasing the value will cause merging to happen %% more often. %% %% Default is: `60` {mapping, "bitcask.merge.triggers.fragmentation", "bitcask.frag_merge_trigger", [ {datatype, integer}, hidden, {default, 60}, {validators, ["is_percentage"]} ]}. {validator, "is_percentage", "must be a percentage", fun(Value) -> Value >= 0 andalso Value =< 100 end}. %% @doc Describes how much data stored for dead keys in a single file %% will trigger merging. The value is in bytes. If a file meets or %% exceeds the trigger value for dead bytes, merge will be %% triggered. Increasing the value will cause merging to occur less %% often, whereas decreasing the value will cause merging to happen %% more often. %% %% When either of these constraints are met by any file in the %% directory, Bitcask will attempt to merge files. %% %% Default is: 512MB {mapping, "bitcask.merge.triggers.dead_bytes", "bitcask.dead_bytes_merge_trigger", [ {datatype, bytesize}, hidden, {default, "512MB"} ]}. %% @doc Describes what ratio of dead keys to total keys in a file will %% cause it to be included in the merge. The value of this setting is %% a percentage (0-100). For example, if a data file contains 4 dead %% keys and 6 live keys, it will be included in the merge at the %% default ratio. Increasing the value will cause fewer files to be %% merged, decreasing the value will cause more files to be merged. %% %% Default is: `40` {mapping, "bitcask.merge.thresholds.fragmentation", "bitcask.frag_threshold", [ {datatype, integer}, hidden, {default, 40}, {validators, ["is_percentage"]} ]}. %% @doc Describes the minimum amount of data occupied by dead keys in %% a file to cause it to be included in the merge. Increasing the %% value will cause fewer files to be merged, decreasing the value %% will cause more files to be merged. %% %% Default is: 128MB {mapping, "bitcask.merge.thresholds.dead_bytes", "bitcask.dead_bytes_threshold", [ {datatype, bytesize}, hidden, {default, "128MB"} ]}. %% @doc Describes the minimum size a file must have to be _excluded_ %% from the merge. Files smaller than the threshold will be %% included. Increasing the value will cause _more_ files to be %% merged, decreasing the value will cause _fewer_ files to be merged. %% %% Default is: 10MB {mapping, "bitcask.merge.thresholds.small_file", "bitcask.small_file_threshold", [ {datatype, bytesize}, hidden, {default, "10MB"} ]}. %% @doc Fold keys thresholds will reuse the keydir if another fold was %% started less than `fold.max_age` ago and there were less than %% `fold.max_puts` updates. Otherwise it will wait until all current %% fold keys complete and then start. Set either option to unlimited %% to disable. {mapping, "bitcask.fold.max_age", "bitcask.max_fold_age", [ {datatype, [{atom, unlimited}, {duration, ms}]}, hidden, {default, unlimited} ]}. {translation, "bitcask.max_fold_age", fun(Conf) -> case cuttlefish:conf_get("bitcask.fold.max_age", Conf) of unlimited -> -1; I when is_integer(I) -> %% app.config expects microseconds I * 1000; _ -> -1 %% The default, for safety end end }. %% @see bitcask.fold.max_age {mapping, "bitcask.fold.max_puts", "bitcask.max_fold_puts", [ {datatype, [integer, {atom, unlimited}]}, hidden, {default, 0} ]}. {translation, "bitcask.max_fold_puts", fun(Conf) -> case cuttlefish:conf_get("bitcask.fold.max_puts", Conf) of unlimited -> -1; I when is_integer(I) -> I; _ -> 0 %% default catch end end }. %% @doc By default, Bitcask keeps all of your data around. If your %% data has limited time-value, or if for space reasons you need to %% purge data, you can set the `expiry` option. If you needed to %% purge data automatically after 1 day, set the value to `1d`. %% %% Default is: `off` which disables automatic expiration {mapping, "bitcask.expiry", "bitcask.expiry_secs", [ {datatype, [{atom, off}, {duration, s}]}, hidden, {default, off} ]}. {translation, "bitcask.expiry_secs", fun(Conf) -> case cuttlefish:conf_get("bitcask.expiry", Conf) of off -> -1; I when is_integer(I) -> I; _ -> -1 end end }. %% @doc Require the CRC to be present at the end of hintfiles. %% Setting this to `allow_missing` runs Bitcask in a backward %% compatible mode where old hint files will still be accepted without %% CRC signatures. {mapping, "bitcask.hintfile_checksums", "bitcask.require_hint_crc", [ {default, strict}, {datatype, {enum, [strict, allow_missing]}}, hidden ]}. {translation, "bitcask.require_hint_crc", fun(Conf) -> case cuttlefish:conf_get("bitcask.hintfile_checksums", Conf) of strict -> true; allow_missing -> false; _ -> true end end}. %% @doc By default, Bitcask will trigger a merge whenever a data file %% contains an expired key. This may result in excessive merging under %% some usage patterns. To prevent this you can set the %% `bitcask.expiry.grace_time` option. Bitcask will defer triggering %% a merge solely for key expiry by the configured number of %% seconds. Setting this to `1h` effectively limits each cask to %% merging for expiry once per hour. %% %% Default is: `0` {mapping, "bitcask.expiry.grace_time", "bitcask.expiry_grace_time", [ {datatype, {duration, s}}, hidden, {default, 0} ]}. %% @doc Configure how Bitcask writes data to disk. %% erlang: Erlang's built-in file API %% nif: Direct calls to the POSIX C API %% %% The NIF mode provides higher throughput for certain %% workloads, but has the potential to negatively impact %% the Erlang VM, leading to higher worst-case latencies %% and possible throughput collapse. {mapping, "bitcask.io_mode", "bitcask.io_mode", [ {default, erlang}, {datatype, {enum, [erlang, nif]}} ]}. bitcask-2.1.0/test/0000755000232200023220000000000013655023466014471 5ustar debalancedebalancebitcask-2.1.0/test/bitcask_timeshift.erl0000644000232200023220000000604013655023466020671 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_timeshift). -include_lib("eunit/include/eunit.hrl"). -include("bitcask.hrl"). -compile([export_all, nowarn_export_all]). current_tstamp() -> case erlang:get(meck_tstamp) of undefined -> erlang:error(uninitialized_meck_tstamp); Value -> Value end. next_tstamp() -> Ts = case erlang:get(meck_tstamp) of undefined -> 1; Tstamp -> Tstamp + erlang:get(meck_tstamp_step) end, erlang:put(meck_tstamp, Ts), Ts. set_tstamp(Tstamp) -> erlang:put(meck_tstamp, Tstamp). set_tstamp_step(Step) -> erlang:put(meck_tstamp_step, Step). timeshift_test_() -> {timeout, 60, fun() -> timeshift_test2() end}. timeshift_test2() -> try Dirname = filename:join(?TEST_FILEPATH, "bc.timeshift"), meck:new(bitcask_time, [passthrough]), meck:expect(bitcask_time, tstamp, fun next_tstamp/0), set_tstamp(100), set_tstamp_step(-1), ?cmd("rm -rf " ++ Dirname), Bref = bitcask:open(Dirname, [read_write]), ok = bitcask:put(Bref, <<"k1">>, <<"v1">>), %% Back when the NIF's internal puts depended on timestamps, this %% second put would fail because we've meck'ed time to go backward. %% Nowadays, this put should succeed. ok = bitcask:put(Bref, <<"k1">>, <<"v2">>), bitcask:close(Bref), %% For each of the data files, validate that it has a valid hint file Validate = fun(Fname) -> {ok, S} = bitcask_fileops:open_file(Fname), try ?assert(bitcask_fileops:has_valid_hintfile(S)) after bitcask_fileops:close(S) end end, [Validate(Fname) || {_Ts, Fname} <- bitcask_fileops:data_file_tstamps(Dirname)], %% In our post-wall-clock timestamp world, verify that we read the newer value. Bref2 = bitcask:open(Dirname, [read_write]), {ok, <<"v2">>} = bitcask:get(Bref2, <<"k1">>), bitcask:close(Bref2) after meck:unload() end. bitcask-2.1.0/test/utils.erl0000644000232200023220000000062413655023466016337 0ustar debalancedebalance%%% File : utils.erl %%% Author : Ulf Norell %%% Description : Contains some functions that should be run without being %%% PULSE instrumented. %%% Created : 21 Mar 2012 by Ulf Norell -module(utils). -compile([export_all, nowarn_export_all]). whereis(Name) -> erlang:whereis(Name). unregister(Name) -> erlang:unregister(Name). exit(Reason) -> erlang:exit(Reason). bitcask-2.1.0/test/token.erl0000644000232200023220000000165013655023466016317 0ustar debalancedebalance%%% File : token.erl %%% Author : Ulf Norell %%% Description : %%% Created : 20 Mar 2012 by Ulf Norell -module(token). -export([next_name/0, get_name/0, stop/0]). next_name() -> rpc(next). get_name() -> rpc(get). stop() -> rpc(stop). rpc(Msg) -> case whereis(?MODULE) of undefined -> start(), timer:sleep(1), rpc(Msg); Pid -> Ref = make_ref(), Pid ! {Msg, Ref, self()}, receive {Ref, R} -> R after 1000 -> {error, timeout} end end. start() -> register(?MODULE, spawn(fun() -> init() end)). init() -> loop(mk_name()). loop(Name) -> receive {next, Tag, Pid} -> Next = mk_name(), Pid ! {Tag, Next}, loop(Next); {get, Tag, Pid} -> Pid ! {Tag, Name}, loop(Name); {stop, Tag, Pid} -> Pid ! {Tag, ok} end. mk_name() -> {A, B, C} = os:timestamp(), lists:concat([A, "-", B, "-", C]). bitcask-2.1.0/test/mute.erl0000644000232200023220000000341113655023466016146 0ustar debalancedebalance%%% File : mute.erl %%% Author : Ulf Norell %%% Description : %%% Created : 26 Mar 2012 by Ulf Norell -module(mute). -compile([export_all, nowarn_export_all]). % Call Fun with output suppressed. run(Fun) -> Ref = make_ref(), Leader = group_leader(), Io = spawn(?MODULE, dummy_io_server, [Leader]), group_leader(Io, self()), Res = (catch {Ref, Fun()}), Io ! stop, group_leader(Leader, self()), case Res of {Ref, X} -> X; {'EXIT', Err} -> exit(Err); Err -> throw(Err) end. dummy_io_server(Leader) -> Reply = fun(From, ReplyAs, X) -> From ! {io_reply, ReplyAs, X}, dummy_io_server(Leader) end, Forward = fun(From, ReplyAs, Request) -> Leader ! {io_request, From, ReplyAs, Request}, dummy_io_server(Leader) end, receive {io_request, From, ReplyAs, Request} -> case Request of {put_chars, _, _} -> Reply(From, ReplyAs, ok); {put_chars, _, _, _, _} -> Reply(From, ReplyAs, ok); {put_chars, _} -> Reply(From, ReplyAs, ok); {put_chars, _, _, _} -> Reply(From, ReplyAs, ok); {requests, Reqs} -> IsOutput = fun(Rq) when is_tuple(Rq) -> case tuple_to_list(Rq) of [put_chars|_] -> true; _ -> false end; (_) -> false end, case [ Rq || Rq <- Reqs, not IsOutput(Rq) ] of [] -> Reply(From, ReplyAs, ok); Rqs -> Forward(From, ReplyAs, {requests, Rqs}) end; _ -> % Forward any other requests Forward(From, ReplyAs, Request) end; stop -> ok; _Unknown -> ?MODULE:dummy_io_server() end. bitcask-2.1.0/test/bitcask_pr156.erl0000644000232200023220000001532113655023466017554 0ustar debalancedebalance-module(bitcask_pr156). -include("bitcask.hrl"). -ifdef(TEST). -compile([export_all, nowarn_export_all]). -include_lib("eunit/include/eunit.hrl"). -endif. %% Number of keys used in the tests -define(NUM_KEYS, 50). %% max_file_size given to bitcask. -define(FILE_SIZE, 400). pr156_regression1_test_() -> %% This is a test for a setuid-bit regression late in the %% dev/testing cycle for PR 156. It is adapted from a %% bitcask_pulse.erl counterexample. Given the fragility of %% reusing eqc:check/2 if the model ever changes, I felt it best %% to make a standalone EUnit test that will be much more stable. %% %% Run the test 5 times, to try to avoid getting lucky in an %% unlikely race with the 'bitcask_merge_delete' server. {timeout, 120, fun() -> [ok = pr156_regression1(X) || X <- lists:seq(1,5)] end}. pr156_regression2_test_() -> {timeout, 120, fun() -> [ok = pr156_regression2(X) || X <- lists:seq(1,5)] end}. pr156_regression1(X) -> io:format("pr156_regression1 ~p at ~p\n", [X, os:timestamp()]), token:next_name(), Dir = ?TEST_FILEPATH ++ ".1." ++ token:get_name(), os:cmd("rm -rf " ++ Dir), bitcask_time:test__set_fudge(10), V3 = goo({call,bitcask_pulse,bc_open,[Dir]}), try _V7 = goo({call,bitcask_pulse,puts,[V3,{1,6},<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>]}), _V13 = goo({call,bitcask_pulse,puts,[V3,{1,7},<<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>]}), _V15 = goo({call,bitcask_pulse,delete,[V3,2]}), _V16 = goo({call,bitcask_pulse,merge,[V3,Dir]}), _V20 = goo({call,bitcask_pulse,delete,[V3,1]}), _V21 = goo({call,bitcask_pulse,make_merge_txt,[Dir,{{335,217,333},90}]}), _V23 = goo({call,bitcask_pulse,puts,[V3,{3,9},<<0,0,0,0,0,0,0>>]}), _V24 = goo({call,bitcask_pulse,merge,[V3,Dir]}), _V25 = goo({call,bitcask_pulse,bc_close,[V3]}), V31 = goo({call,bitcask_pulse,bc_open,[Dir]}), try V32 = goo({call,bitcask_pulse,fold_keys,[V31]}), _V33 = goo({call,bitcask_pulse,bc_close,[V31]}), Res = lists:sort(V32), ?assertEqual([3,4,5,6,7,8,9], Res), ok after _ = (catch bitcask_pulse:bc_close(V31)) end after _ = (catch bitcask_pulse:bc_close(V3)), bitcask_time:test__clear_fudge(), os:cmd("rm -rf " ++ Dir) end. %% !@#$!@%!, this is really irritating. pr156_regression2() fails %% consistently on my MBP + SSD if bitcask:merge_single_entry() is %% buggy. But if that same bug is present, pr156_regression2() passes %% on a slower machine. Ditto for buildbot. {sigh} %% My MBP executes each of the N iterations in about 200-300 msec. %% r1s11.bos1 executes each of N iterations in about 1500 msec. pr156_regression2(X) -> io:format("pr156_regression2 ~p at ~p\n", [X, os:timestamp()]), token:next_name(), Dir = ?TEST_FILEPATH ++ ".2." ++ token:get_name(), os:cmd("rm -rf " ++ Dir), bitcask_time:test__set_fudge(10), try V1 = goo({call,bitcask_pulse,bc_open,[Dir,{true,{413,255,375},100}]}), _V3 = goo({call,bitcask_pulse,puts,[V1,{1,8},<<0,0,0,0,0>>]}), _V8 = goo({call,bitcask_pulse,bc_close,[V1]}), V15 = goo({call,bitcask_pulse,bc_open,[Dir,{true,{133,23,388},1}]}), _V21 = goo({call,bitcask_pulse,puts,[V15,{1,20},<<0,0,0,0,0>>]}), _V43 = goo({call,bitcask_pulse,merge,[V15,Dir]}), _V46 = goo({call,bitcask_pulse,puts,[V15,{1,2},<<0,0,0,0,0,0,0,0,0,0,0,0>>]}), _V49 = goo({call,bitcask_pulse,bc_close,[V15]}), V50 = goo({call,bitcask_pulse,bc_open,[Dir,{true,{414,120,104},4}]}), _V51 = goo({call,bitcask_pulse,delete,[V50,1]}), _V52 = goo({call,bitcask_pulse,merge,[V50,Dir]}), _V56 = goo({call,bitcask_pulse,delete,[V50,2]}), _V60 = goo({call,bitcask_pulse,bc_close,[V50]}), V84 = goo({call,bitcask_pulse,bc_open,[Dir,{true,{291,81,8},40}]}), _V90 = goo({call,bitcask_pulse,merge,[V84,Dir]}), _V93 = goo({call,bitcask_pulse,bc_close,[V84]}), V95 = goo({call,bitcask_pulse,bc_open,[Dir,{Dir,{190,6,52},1}]}), [not_found,not_found] = goo({call,bitcask_pulse,gets,[V95,{1,2}]}), _V100 = goo({call,bitcask_pulse,bc_close,[V95]}), ok after bitcask_time:test__clear_fudge(), os:cmd("rm -rf " ++ Dir) end. nice_key(K) -> list_to_binary(io_lib:format("kk~2.2.0w", [K])). un_nice_key(<<"kk", Num:2/binary>>) -> list_to_integer(binary_to_list(Num)). put(H, K, V) -> ok = bitcask:put(H, nice_key(K), V). get(H, K) -> bitcask:get(H, nice_key(K)). needs_merge_wrapper(H) -> case check_no_tombstones(H, ok) of ok -> bitcask:needs_merge(H); Else -> {needs_merge_wrapper_error, Else} end. check_no_tombstones(Ref, Good) -> Res = bitcask:fold_keys(Ref, fun(K, Acc0) -> [K|Acc0] end, [], -1, -1, true), case [X || {tombstone, _} = X <- Res] of [] -> Good; Else -> {check_no_tombstones, Else} end. make_merge_txt(Dir, _Seed, Probability) -> % random:seed(Seed), case filelib:is_dir(Dir) of true -> DataFiles = filelib:wildcard("*.data", Dir), {ok, FH} = file:open(Dir ++ "/merge.txt", [write]), [case rand:uniform(100) < Probability of true -> io:format(FH, "~s\n", [DF]); false -> ok end || DF <- DataFiles], ok = file:close(FH); false -> ok end. goo({_, _, bc_open, [Dir]}) -> bitcask:open(Dir, [read_write, {max_file_size, ?FILE_SIZE}, {open_timeout, 1234}]); goo({_, _, bc_open, [Dir,{DoMergeP,X,Y}]}) -> if DoMergeP -> make_merge_txt(Dir, X, Y); true -> ok end, bitcask:open(Dir, [read_write, {max_file_size, ?FILE_SIZE}, {open_timeout, 1234}]); goo({_, _, bc_close, [H]}) -> bitcask:close(H); goo({_, _, puts, [H, {K1, K2}, V]}) -> case lists:usort([ put(H, K, V) || K <- lists:seq(K1, K2) ]) of [ok] -> ok; Other -> throw({line, ?LINE, Other}) end; goo({_, _, gets, [H, {Start, End}]}) -> [get(H, K) || K <- lists:seq(Start, End)]; goo({_, _, delete, [H, K]}) -> ok = bitcask:delete(H, nice_key(K)); goo({_, _, merge, [H,Dir]}) -> case needs_merge_wrapper(H) of {true, Files} -> case catch bitcask:merge(Dir, [], Files) of {'EXIT', Err} -> Err; R -> R end; false -> not_needed end; goo({_, _, make_merge_txt, [Dir,{X,Y}]}) -> make_merge_txt(Dir, X, Y); goo({_, _, fold_keys, [H]}) -> bitcask:fold_keys(H, fun(#bitcask_entry{key = Kb}, Ks) -> [un_nice_key(Kb)|Ks] end, []). bitcask-2.1.0/test/bcfold_setup0000755000232200023220000000260613655023466017074 0ustar debalancedebalance#! /usr/bin/env escript main([BitcaskDir, DataDir]) -> load_bitcask(BitcaskDir), make_small(DataDir), make_medium(DataDir), make_large(DataDir), make_cs(DataDir). load_bitcask(Dir) -> Path = Dir ++ "/ebin/", Modlist = filelib:fold_files(Path, ".*.beam", false, fun(X, Acc) -> Mod0 = filename:rootname(filename:basename(X)), Mod = list_to_atom(Mod0), [Mod | Acc] end, []), code:add_path(Path), lists:map(fun code:load_abs/1, Modlist). make_small(Dir) -> make_dir(Dir++"/small/", 32, 100). make_medium(Dir) -> make_dir(Dir++"/medium/", 32, 2048). make_large(Dir) -> make_dir(Dir++"/large/", 32, 50*1024). make_cs(Dir) -> make_dir(Dir++"/cs/", 32, 1024*1024). make_dir(Dir, KeySz, BinSz) -> Ref = bitcask:open(Dir, [read_write]), %% header size + key size + binary size + ext term format overhead BinarySize = 14 + KeySz + BinSz + 12, %% 1GB NumObjs = (1073741824 div BinarySize) + 1, Key = crypto:strong_rand_bytes(KeySz), Bin = crypto:strong_rand_bytes(BinSz), [bitcask:put(Ref, <>, Bin) || X <- lists:seq(1, NumObjs)], bitcask:close(Ref). bitcask-2.1.0/test/bctt0000755000232200023220000005727613655023466015374 0ustar debalancedebalance#! /usr/bin/env escript %% -*- erlang -*- %%! -pz ../bitcask %% %% Bitcask Torture Tests %% %% Pre-populates a bitcask with a known range of keys, the value for %% each key stores the key itself and a sequence number for the write %% (initially 1). %% %% The test then continually rewrites all keys with the next sequence number %% while other processes read, fold over and merge the cask. %% %% Parameters: %% %% duration - number of milliseconds to run the test for. If 0 then %% only the initial write will take place. Default 0 ms. %% %% max_file_size - maximum file size within the bitcask in bytes. %% Default 10Mb. %% %% stats_freq - how often to output test status. Interval in ms. Default %% 10,000 ms. %% %% cask - name of bitcask to open %% %% num_keys - number of keys used in test %% %% readers - number of threads reading from the bitcask. Default 0. %% %% folders - number of threads calling bitcask:fold. Each fold checks %% all keys visited only once. A new process is created for %% each fold. Default 0. %% %% foldkeys - number of threads calling bitcask:fold. Each fold checks %% all keys visited only once. A new process is created for %% each fold. Default 0. %% %% mergers - if non-zero run a single merge thread. The merge thread %% is kicked by the writer thread if needs_merge returns %% true and passes the returned merge filenames. Default 0. %% %% needs_merge_freq - writer thread calls needs_merge after needs_merge_freq %% writes to ensure files are closed after merges. %% Default 1000. %% %% frag_merge_trigger - bitcask app env variables - set before running %% dead_bytes_merge_trigger %% frag_threshold %% dead_bytes_threshold %% small_file_threshold %% -module(bctt). -compile([export_all]). -include_lib("bitcask/include/bitcask.hrl"). -record(state, {seq = 0, reader_reps=0, folder_reps=0, fold_keys_reps=0, merger_reps=0, merges_pending=0, duration, status_freq = 10000, cask = "bctt.bc", num_keys=16384, writer_pid, restart_writer=false, writers=1, needs_merge_freq = 1000, frag_merge_trigger, % Env default trigger on 60% fragmentation dead_bytes_merge_trigger, % Env default trigger on dead bytes > 512 MB frag_threshold, % Include merge >= 40% fragmentation dead_bytes_threshold, % Include merge dead bytes > 128 MB small_file_threshold, % Include merge file is < 10 MB max_fold_age = -1, % Frozen keydir can be as old as you like max_fold_puts = 0, % as long as there are no updates readers=0, folders=0, foldkeys=0, mergers=0, max_file_size=10*1024*1024, open_timeout=undefined}). %% Function to run from inside beam test() -> test([]). test(Opts) -> {ok, State} = process_args(Opts, #state{}), print_params(State), proc_lib:spawn(fun() -> do_test(State) end). %% Escript version main() -> main([]). main(Args) -> io:format("PWD: ~p\n", [file:get_cwd()]), try case process_args([parse_arg(Arg) || Arg <- Args], #state{}) of {ok, State} -> print_params(State), ensure_bitcask(), ensure_deps(), set_bitcask_env(State), do_test(State); syntax -> syntax(), halt(0) end catch _:Why -> io:format("Failed: ~p\n", [Why]), halt(1) end. syntax() -> io:format("bctt [duration=Msecs]\n" " [cask=CaskFile]\n" " [num_keys=NumKeys]\n" " [readers=NumReaders]\n" " [folders=NumFolders]\n" " [foldkeys=NumFoldKeys]\n" " [mergers=NumMergers]\n" " [max_file_size=Bytes] # set 0 for defaults\n" " [open_timeout=Secs]\n" " [restart_writer=true|false]\n"). do_test(State0) -> erlang:process_flag(trap_exit, true), os:cmd("rm -rf " ++ State0#state.cask), %% Put a base of keys - each value has {Key, Seq}, starting from 1. io:format("\nInitial populate.\n"), State1 = start_writer(State0), kick_writer(State1), wait_for_writer(State1), %% Start continually rewriting the keys and optionally reading, %% folding and merging State = State1#state{seq = 1}, start_merger(State, State#state.mergers), kick_writer(State), start_readers(State, State#state.readers), start_folders(State, State#state.folders), start_fold_keys(State, State#state.foldkeys), schedule_status(State), case State#state.duration of undefined -> self() ! stop; Duration -> timer:send_after(Duration, self(), stop) end, io:format("Starting test.\n"), EndState = restart_procs(State), stop_writer(EndState), wait_for_procs(EndState). restart_procs(State) -> receive Msg -> ok end, try case Msg of stop -> io:format("Test ending...\n"), State; status -> io:format("Writer seq: ~p Readers=~p Folders=~p FoldKeys=~p Merges=~p MergesPending=~p\n", [State#state.seq, State#state.reader_reps, State#state.folder_reps, State#state.fold_keys_reps, State#state.merger_reps, State#state.merges_pending]), schedule_status(State), restart_procs(State); {write_done, WriteSeq} -> true = (State#state.seq+1 =:= WriteSeq), NewState = State#state{seq = WriteSeq}, case State#state.restart_writer of true -> stop_writer(State); false -> kick_writer(NewState) end, restart_procs(NewState); write_exit -> State1 = start_writer(State), kick_writer(State1), restart_procs(State1); merge_pending -> restart_procs(State#state{merges_pending = State#state.merges_pending+1}); merge_done -> restart_procs(State#state{merges_pending = State#state.merges_pending-1, merger_reps = State#state.merger_reps+1}); read_done -> start_readers(State, 1), restart_procs(State#state{reader_reps = State#state.reader_reps+1}); fold_done -> start_folders(State, 1), restart_procs(State#state{folder_reps = State#state.folder_reps+1}); fold_keys_done -> start_fold_keys(State, 1), restart_procs(State#state{fold_keys_reps = State#state.fold_keys_reps+1}); {'EXIT', _From, normal} -> restart_procs(State); {'EXIT', From, Reason} -> io:format("Test process ~p died: ~p\nState: ~p\n", [From, Reason, State]), State; Other -> io:format("Restart procs got unexpected message: ~p\n", [Other]), State end catch What:Err -> io:format("restart_proc: ~p ~p\nMsg: ~p\nState: ~p\n", [What, Err, Msg, State]), State end. %% Wait for the initial writer to complete - the os:cmd call %% can generate an EXIT message wait_for_writer(State) -> WriterPid = State#state.writer_pid, receive {'EXIT', WriterPid, Why} -> erlang:error({initial_write_failed, Why}); {'EXIT', _Pid, _Why} -> wait_for_writer(State); {write_done, 1} -> ok end. wait_for_procs(#state{writers = 0, readers = 0, folders = 0, foldkeys = 0, merges_pending = 0}) -> catch merger ! exit, io:format("Test complete\n"); wait_for_procs(State) -> receive stop -> io:format("Wait for procs got stop message pid ~p\n", [self()]), wait_for_procs(State); status -> wait_for_procs(State); read_done -> wait_for_procs(State#state{readers = State#state.readers - 1}); fold_done -> wait_for_procs(State#state{folders = State#state.folders - 1}); fold_keys_done -> wait_for_procs(State#state{foldkeys = State#state.foldkeys - 1}); merge_pending -> %% The writer could still be adding them as we try to shut down. wait_for_procs(State#state{merges_pending = State#state.merges_pending + 1}); merge_done -> wait_for_procs(State#state{merges_pending = State#state.merges_pending - 1, merger_reps = State#state.merger_reps+1}); {write_done, _WriteSeq} -> wait_for_procs(State); write_exit -> wait_for_procs(State#state{writers = State#state.writers - 1}); {'EXIT', _From, normal} -> wait_for_procs(State); {'EXIT', From, Reason} -> io:format("Test process ~p died\n~p\n", [From, Reason]); Other -> io:format("Wait for procs got unexpected message: ~p\n", [Other]) end. schedule_status(State) -> case State#state.status_freq of undefined -> ok; StatusFreq -> timer:send_after(StatusFreq, self(), status) end. start_writer(State) -> Caller = self(), Pid = proc_lib:spawn_link(fun() -> Opts = writer_opts(State), %% Until closing stale file handles resolved Ref = bitcask:open(State#state.cask, Opts), write_proc(Ref, Caller) end), %%X io:format("Started writer pid ~p\n", [Pid]), State#state{writer_pid = Pid}. writer_opts(State) -> lists:flatten([ [read_write], case State#state.max_file_size of Size when is_integer(Size), Size > 0-> [{max_file_size, Size}]; _ -> [] end, case State#state.open_timeout of OpenTimeout when is_integer(OpenTimeout) -> [{open_timeout, OpenTimeout}]; _ -> [] end]). start_readers(_State, 0) -> ok; start_readers(State, NumReaders) -> Caller = self(), spawn_worker(reader, fun() -> read_proc(State#state.cask, State#state.num_keys, Caller) end), start_readers(State, NumReaders - 1). start_folders(_State, 0) -> ok; start_folders(State, NumFolders) -> Caller = self(), spawn_worker(folder, fun() -> fold_proc(State#state.cask, State#state.num_keys, Caller) end), start_folders(State, NumFolders - 1). start_fold_keys(_State, 0) -> ok; start_fold_keys(State, NumFoldKeys) -> Caller = self(), spawn_worker(fold_keys, fun() -> fold_keys_proc(State#state.cask, State#state.num_keys, Caller) end), start_fold_keys(State, NumFoldKeys - 1). start_merger(_State, 0) -> ok; % no merger, messages to it will be dropped start_merger(State, _NumMergers) -> % non-zero mergers kicks it off Caller = self(), spawn_worker(merger, fun() -> register(merger, self()), merge_proc(State#state.cask, Caller) end). spawn_worker(Type, Fun) -> proc_lib:spawn(fun() -> try Fun() catch _:Err -> io:format("~p CRASHED ~p: ~p\n", [Type, self(), Err]), throw(Err) end end). kick_writer(State) -> State#state.writer_pid ! {start, State#state.seq + 1, State#state.num_keys, State#state.needs_merge_freq}. stop_writer(State) -> %%X io:format("Stopping writer ~p\n", [State#state.writer_pid]), MRef = erlang:monitor(process, State#state.writer_pid), State#state.writer_pid ! stop, receive {'DOWN', MRef, _, _, _} -> %%X io:format("Stopped writer ~p\n", [State#state.writer_pid]), ok after 60000 -> erlang:error({writer_pid_timeout, State#state.writer_pid}) end. write_proc(Ref, Caller) -> receive stop -> %%X io:format("Writer ~p received stop request\n", [self()]), Caller ! write_exit; {start, Seq, NumKeys, NeedsMergeFreq} -> %% io:format("Writer starting\n"), write(Ref, NumKeys, Seq, NeedsMergeFreq, Caller), application:set_env(bitcask, minseq, Seq), Caller ! {write_done, Seq}, write_proc(Ref, Caller) end. write(_Ref, 0, _Seq, _NeedsMergeFreq, _Caller) -> ok; write(Ref, Key, Seq, NeedsMergeFreq, Caller) -> try case (Key rem NeedsMergeFreq) == 0 of true -> case bitcask:needs_merge(Ref) of {true, Filenames} -> Caller ! merge_pending, %% Try and kick the merger, may %% not be configured to run. catch merger ! {kick_merge, Filenames}; false -> ok end; _ -> ok end catch _:Err1 -> io:format(user, "NeedsMerge ~p: ~p\n", [Key, Err1]) end, try ok = bitcask:put(Ref, <>, term_to_binary({Key, Seq})) catch _:Err2 -> io:format(user, "Put ~p: ~p\n", [Key, Err2]) end, write(Ref, Key - 1, Seq, NeedsMergeFreq, Caller). read_proc(Cask, NumKeys, Caller) -> Ref = bitcask:open(Cask), {ok, Seq} = application:get_env(bitcask, minseq), %io:format("read_proc starting: minseq=~p\n", [Seq]), read(Ref, NumKeys, Seq), bitcask:close(Ref), Caller ! read_done. read(_Ref, 0, _MinSeq) -> ok; read(Ref, Key, MinSeq) -> try {ok, Bin} = bitcask:get(Ref, <>), {Key, Seq} = binary_to_term(Bin), case Seq >= MinSeq of true -> ok; false -> io:format("Read ~p Got: ~p Expected(>=): ~p\n", [Key, Seq, MinSeq]) end catch _:Err -> io:format(user, "Get ~p: ~p\n", [Key, Err]) end, read(Ref, Key - 1, MinSeq). fold_proc(Cask, NumKeys, Caller) -> Ref = bitcask:open(Cask), fold(Ref, NumKeys), bitcask:close(Ref), Caller ! fold_done. fold(Ref, NumKeys) -> {ok, MinSeq} = application:get_env(bitcask, minseq), Folder = fun(<>, Bin, Keys) -> %% Lookup minium sequence on first pass through {Key, Seq} = binary_to_term(Bin), case Seq >= MinSeq of true -> ok; _ -> io:format("fold returned early seq for ~p. " "MinExpected: ~p Returned: ~p\n", [Key, MinSeq, Seq]) end, [Key | Keys] end, FoldedKeys = bitcask:fold(Ref, Folder, []), check_fold(1, NumKeys, lists:sort(FoldedKeys)). fold_keys_proc(Cask, NumKeys, Caller) -> Ref = bitcask:open(Cask), fold_keys(Ref, NumKeys), bitcask:close(Ref), Caller ! fold_keys_done. fold_keys(Ref, NumKeys) -> Folder = fun(#bitcask_entry{key = <>}, Keys) -> %io:format(user, "FoldedKey: ~p\n", [Key]), [Key | Keys] end, FoldedKeys = bitcask:fold_keys(Ref, Folder, []), check_fold(1, NumKeys, lists:sort(FoldedKeys)). check_fold(Key, MaxKey, []) when Key == MaxKey + 1 -> ok; check_fold(Key, _MaxKey, []) -> io:format("Fold missing key ~p (stopping searching)\n", [Key]); check_fold(Key, MaxKey, [Key | Rest]) -> check_fold(Key + 1, MaxKey, Rest); check_fold(Key1, _MaxKey, [_Key2 | _Rest]) -> io:format("Fold missing key ~p (stopping searching)\n", [Key1]). merge_proc(Cask, Caller) -> receive {kick_merge, Filenames} -> try ok = bitcask:merge(Cask, [], Filenames) catch %% Maybe keydir is being loaded _:{error, not_ready} -> ok end, Caller ! merge_done, merge_proc(Cask, Caller); stop -> ok end. ensure_bitcask() -> case code:ensure_loaded(bitcask) of {module, bitcask} -> ok; _ -> {ok, Cwd} = file:get_cwd(), find_bitcask(filename:split(Cwd)) end. %% Look for bitcask.beam in Cwd and Cwd/ebin find_bitcask(["/"]) -> erlang:error("Could not find bitcask\n"); find_bitcask(Cwd) -> case try_bitcask_dir(Cwd) of true -> ok; false -> case try_bitcask_dir(Cwd ++ ["ebin"]) of true -> ok; false -> find_bitcask(parent_dir(Cwd)) end end. try_bitcask_dir(Dir) -> CodeDir = filename:join(Dir), Beam = bitcask_beam(CodeDir), io:format("Looking for bitcask in \"~s\".\n", [CodeDir]), case filelib:is_regular(Beam) of true -> io:format("Adding bitcask dir \"~s\".\n", [CodeDir]), code:add_pathz(CodeDir), {module, bitcask} = code:ensure_loaded(bitcask), true; _ -> false end. ensure_deps() -> BitcaskBeam = code:where_is_file("bitcask.beam"), BitcaskDir = parent_dir(filename:split(filename:dirname(BitcaskBeam))), Pattern = filename:join(BitcaskDir) ++ "/deps/*/ebin", Deps = filelib:wildcard(Pattern), AddDepDir = fun(DepDir) -> io:format("Adding dependency dir \"~s\".\n", [DepDir]), code:add_pathz(DepDir) end, lists:foreach(AddDepDir, Deps). parent_dir([]) -> ["/"]; parent_dir(["/"]) -> ["/"]; parent_dir(Dirs) -> lists:reverse(tl(lists:reverse(Dirs))). bitcask_beam(Cwd) -> filename:join(Cwd, ["bitcask" ++ code:objfile_extension()]). set_bitcask_env(State) -> application:load(bitcask), set_env(frag_merge_trigger, State#state.frag_merge_trigger), set_env(dead_bytes_merge_trigger, State#state.dead_bytes_merge_trigger), set_env(frag_threshold, State#state.frag_threshold), set_env(dead_bytes_threshold, State#state.dead_bytes_threshold), set_env(small_file_threshold, State#state.small_file_threshold), set_env(max_fold_age, State#state.max_fold_age), set_env(max_fold_puts, State#state.max_fold_puts), io:format("Bitcask AppEnv:\n~p\n", [application:get_all_env(bitcask)]). set_env(_, undefined) -> ok; set_env(Var, Val) -> application:set_env(bitcask, Var, Val). process_args([], State) -> {ok, State}; process_args([Arg | Rest], State) -> case process_arg(Arg, State) of {ok, NewState} -> process_args(Rest, NewState); Reason -> Reason end. process_arg({help, _}, _State) -> syntax; process_arg({Name, Val}, State) when Name =:= duration; Name =:= status_freq; Name =:= num_keys; Name =:= readers; Name =:= folders; Name =:= foldkeys; Name =:= mergers; Name =:= max_file_size; Name =:= open_timeout; Name =:= needs_merge_freq; Name =:= frag_merge_trigger; Name =:= dead_bytes_merge_trigger; Name =:= frag_threshold; Name =:= dead_bytes_threshold; Name =:= small_file_threshold -> case is_integer(Val) of true -> {ok, setelement(get_state_index(Name), State, Val)}; false -> {ok, setelement(get_state_index(Name), State, list_to_integer(Val))} end; process_arg({Name, Val}, State) when Name =:= restart_writer -> case is_atom(Val) of true -> {ok, setelement(get_state_index(Name), State, Val)}; false -> {ok, setelement(get_state_index(Name), State, list_to_atom(Val))} end; process_arg({Name, Val}, State) -> {ok, setelement(get_state_index(Name), State, Val)}. parse_arg([$- | Arg]) -> % strip leading --'s parse_arg(Arg); parse_arg(Arg) -> case string:tokens(Arg, "=") of [NameStr] -> Val = undefined; [NameStr, Val] -> ok; _ -> NameStr = Val = undefined, erlang:error({badarg, Arg}) end, case catch list_to_existing_atom(NameStr) of {'EXIT', {badarg, _}} -> Name = undefined, erlang:error({badarg, NameStr}); Name -> ok end, {Name, Val}. get_state_index(Name) -> get_state_index(Name, 2, record_info(fields, state)). % first element is 'state' get_state_index(Name, _Index, []) -> io:format("Cannot find index in state for ~p\n", [Name]), erlang:error({badarg, Name}); get_state_index(Name, Index, [Name | _Rest]) -> Index; get_state_index(Name, Index, [_OtherName | Rest]) -> get_state_index(Name, Index + 1, Rest). print_params(State) -> io:format("Bitcask Test\n"), io:format("duration: ~s\n", [format_duration(State#state.duration)]), io:format("cask: ~s\n", [State#state.cask]), io:format("num_keys: ~b\n", [State#state.num_keys]), io:format("readers: ~b\n", [State#state.readers]), io:format("folders: ~b\n", [State#state.folders]), io:format("foldkeys: ~b\n", [State#state.foldkeys]), io:format("mergers: ~b\n", [State#state.mergers]), io:format("max_file_size: ~s\n", [format_max_file_size(State#state.max_file_size)]), io:format("needs_merge_freq: ~b\n", [State#state.needs_merge_freq]), io:format("open_timeout: ~s\n", [format_timeout(State#state.open_timeout)]), io:format("\n"). format_duration(undefined) -> "once"; format_duration(Duration) -> io_lib:format("~p ms", [Duration]). format_timeout(undefined) -> "default"; format_timeout(Secs) -> io_lib:format("~b s", [Secs]). format_max_file_size(MaxFileSize) when is_integer(MaxFileSize), MaxFileSize > 0 -> io_lib:format("~b", [MaxFileSize]); format_max_file_size(_) -> "default". bitcask-2.1.0/test/bcfold_perf0000755000232200023220000000557113655023466016674 0ustar debalancedebalance#!/usr/bin/env escript -mode(compile). main(Args) when length(Args) >= 2 -> [DataDir | List] = Args, [begin test_small(DataDir, D), test_medium(DataDir,D), test_large(DataDir,D), test_cs(DataDir,D) end || D <- List]; main(_) -> usage(). usage() -> io:format("bcfold_perf +~n"), io:format("run bcfold_setup before running this~n"). test(DataDir, BranchDir, Subdir) -> Modlist = ensure_and_load_bitcask(BranchDir), time_folds(DataDir, Subdir), cleanup(Modlist). test_small(DataDir, BranchDir) -> test(DataDir, BranchDir, "small"). test_medium(DataDir, BranchDir) -> test(DataDir, BranchDir, "medium"). test_large(DataDir, BranchDir) -> test(DataDir, BranchDir, "large"). test_cs(DataDir, BranchDir) -> test(DataDir, BranchDir, "cs"). sum(_, _, A0) -> A0 + 1. sum(_, _, _, A0) -> A0 + 1. time_folds(DataDir, SubDir) -> clean_env(), %% this is basically a proxy for hintfile folds and %% the CRC check {OpenTime, Ref} = timer:tc(bitcask, open, [DataDir++SubDir++"/"]), %%bitcask:close(Ref), clean_env(), {ok, Fd1} = bitcask_fileops:open_file(DataDir++SubDir++"/1.bitcask.data"), {FoldDatafileKeyTime, Count} = timer:tc(bitcask_fileops, fold_keys, [Fd1, fun sum/4, 0, datafile]), bitcask_io:file_close(Fd1), clean_env(), {FoldDatafileTime, Count2} = timer:tc(bitcask, fold, [Ref, fun sum/3, 0]), io:format("Report for run: ~s~n", [SubDir]), io:format("Open: ~15w~n", [OpenTime]), io:format("Datafile Key Fold: ~15w~10w~n", [FoldDatafileKeyTime, Count]), io:format("Datafile Fold: ~15w~10w~n~n", [FoldDatafileTime, Count2]). clean_env() -> case os:type() of {unix, darwin} -> os:cmd("purge"); {unix, linux} -> io:format("if not root or called with sudo, this may fail~n"), os:cmd("echo 3 >/proc/sys/vm/drop_caches"); _ -> halt("unknown os") end. ensure_and_load_bitcask(BranchDir) -> Path = BranchDir ++ "/ebin/", Modlist = filelib:fold_files(Path, ".*.beam", false, fun(X, Acc) -> Mod0 = filename:rootname(filename:basename(X)), Mod = list_to_atom(Mod0), [Mod | Acc] end, []), code:add_path(Path), lists:map(fun code:load_abs/1, Modlist), code:del_path(Path), application:start(bitcask), application:set_env(bitcask, io_mode, erlang), Modlist. cleanup(Modlist) -> [begin code:delete(M), code:purge(M) end || M <- Modlist]. bitcask-2.1.0/test/handle_errors.erl0000644000232200023220000001225013655023466020024 0ustar debalancedebalance%%%------------------------------------------------------------------- %%% @author Hans Svensson <> %%% @copyright (C) 2012, Hans Svensson %%% @doc %%% %%% @end %%% Created : 19 Mar 2012 by Hans Svensson <> %%%------------------------------------------------------------------- -module(handle_errors). -behaviour(gen_event). %% API -export([start_link/0, add_handler/0]). %% gen_event callbacks -export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(state, { errors = [] :: list() }). %%%=================================================================== %%% gen_event callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @doc %% Creates an event manager %% %% @spec start_link() -> {ok, Pid} | {error, Error} %% @end %%-------------------------------------------------------------------- start_link() -> gen_event:start_link({local, ?SERVER}). %%-------------------------------------------------------------------- %% @doc %% Adds an event handler %% %% @spec add_handler() -> ok | {'EXIT', Reason} | term() %% @end %%-------------------------------------------------------------------- add_handler() -> gen_event:add_handler(?SERVER, ?MODULE, []). %%%=================================================================== %%% gen_event callbacks %%%=================================================================== %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever a new event handler is added to an event manager, %% this function is called to initialize the event handler. %% %% @spec init(Args) -> {ok, State} %% @end %%-------------------------------------------------------------------- init([]) -> {ok, #state{}}. %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever an event manager receives an event sent using %% gen_event:notify/2 or gen_event:sync_notify/2, this function is %% called for each installed event handler to handle the event. %% %% @spec handle_event(Event, State) -> %% {ok, State} | %% {swap_handler, Args1, State1, Mod2, Args2} | %% remove_handler %% @end %%-------------------------------------------------------------------- handle_event({error, _, {_, "Hintfile '~s' has bad CRC" ++ _, _}}, State) -> {ok, State}; handle_event({error, _, {_, "** Generic server" ++ _, _}}, State) -> {ok, State}; handle_event({error, _, {_, "Failed to merge ~p: ~p\n", [_, not_ready]}}, State) -> {ok, State}; handle_event({error, _, {_, "Failed to merge ~p: ~p\n", [_, {merge_locked, _, _}]}}, State) -> {ok, State}; handle_event({error, _, {_, "Failed to read lock data from ~s: ~p\n", [_, {invalid_data, <<>>}]}}, State) -> {ok, State}; handle_event({error, _, Event}, State) -> {ok, State#state{ errors = [Event|State#state.errors] }}; handle_event(_Event, State) -> {ok, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever an event manager receives a request sent using %% gen_event:call/3,4, this function is called for the specified %% event handler to handle the request. %% %% @spec handle_call(Request, State) -> %% {ok, Reply, State} | %% {swap_handler, Reply, Args1, State1, Mod2, Args2} | %% {remove_handler, Reply} %% @end %%-------------------------------------------------------------------- handle_call(get_errors, S) -> {ok, S#state.errors, S#state{ errors = [] }}; handle_call(_Request, State) -> Reply = ok, {ok, Reply, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% This function is called for each installed event handler when %% an event manager receives any other message than an event or a %% synchronous request (or a system message). %% %% @spec handle_info(Info, State) -> %% {ok, State} | %% {swap_handler, Args1, State1, Mod2, Args2} | %% remove_handler %% @end %%-------------------------------------------------------------------- handle_info(_Info, State) -> {ok, State}. %%-------------------------------------------------------------------- %% @private %% @doc %% Whenever an event handler is deleted from an event manager, this %% function is called. It should be the opposite of Module:init/1 and %% do any necessary cleaning up. %% %% @spec terminate(Reason, State) -> void() %% @end %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% @private %% @doc %% Convert process state when code is changed %% %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} %% @end %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== bitcask-2.1.0/test/Run-eunit-loop.expect0000755000232200023220000000777513655023466020563 0ustar debalancedebalance#!/usr/bin/expect -- ## usage: $0 eunit-command-as-a-single-string ## ## e.g. ## ## ./priv/Run-eunit-loop.expect "./rebar compile eunit apps=bitcask suite=bitcask_eqc" ## Reminder reminder reminder: never use single quotes ## Reminder reminder reminder: never use single quotes ## Reminder reminder reminder: never use single quotes set default_timeout 120 set timeout $default_timeout match_max -d 200100100 set eunit_command [lindex $argv 0] set threshold [lindex $argv 1] set sleeptime [lindex $argv 2] spawn bash set main_session $spawn_id proc msg_to_user {msg} { send_user "\n\n*** [exec date] $msg\r\n" } proc echo_and_say {msg} { msg_to_user $msg spawn say "$msg" expect eof upvar 1 main_session main_session set spawn_id $main_session } proc wait_for_shell_prompt {} { # My bash shell prompt looks like this: "bash-3.2$ " set bash_prompt_string "bash" expect $bash_prompt_string } msg_to_user "Now starting" set count 0 while {1} { send "$eunit_command\r" incr count msg_to_user "Running number $count" while {1} { expect { {Segmentation} { echo_and_say "Segmentation violation" #puts "cp /tmp/slf-stuff-just-in-case ./zzz.sigsegv.[exec date +%s]" #exec cp /tmp/slf-stuff-just-in-case ./zzz.sigsegv.[exec date +%s] sleep 2 wait_for_shell_prompt ; echo_and_say "Got prompt" break } {size_object} { echo_and_say "Bad tag" #puts "cp /tmp/slf-stuff-just-in-case ./zzz.bad-tag.[exec date +%s]" #exec cp /tmp/slf-stuff-just-in-case ./zzz.bad-tag.[exec date +%s] sleep 2 wait_for_shell_prompt ; echo_and_say "Got prompt" break } {deadlock} { echo_and_say "Deadlock" send "\003" ; sleep 1; send "\003" ; sleep 1; wait_for_shell_prompt ; echo_and_say "Got prompt" break } {Invariant broken} { msg_to_user "Invariant broken" #puts "cp /tmp/slf-stuff-just-in-case ./zzz.invariant-broken.[exec date +%s]" #exec cp /tmp/slf-stuff-just-in-case ./zzz.invariant-broken.[exec date +%s] send "\003" ; sleep 1; send "\003" ; sleep 1; wait_for_shell_prompt ; # echo_and_say "Got prompt" break } {Shrinking} { echo_and_say "Bad joss, Taipan. We are shrinking" set timeout 9999999 } {Unknown command for pulse} { set timeout $default_timeout echo_and_say "Drat, unknown command for pulse" #puts "cp /tmp/slf-stuff-just-in-case ./zzz.unknown-command.[exec date +%s]" #exec cp /tmp/slf-stuff-just-in-case ./zzz.unknown-command.[exec date +%s] send "\003" ; sleep 1; send "\003" ; sleep 1; wait_for_shell_prompt ; echo_and_say "Got prompt" break; } {ERROR:} { send_user "Exiting now" echo_and_say "Run finished, please check output" exit 1 } {Test passed} { wait_for_shell_prompt ; echo_and_say "Hooray, test passed" exit 0 } timeout { echo_and_say "Bummer, there was a timeout" #puts "cp /tmp/slf-stuff-just-in-case ./zzz.timeout.[exec date +%s]" #exec cp /tmp/slf-stuff-just-in-case ./zzz.timeout.[exec date +%s] # set foo 15 ; sleep 2 ; echo_and_say "I am going to sleep for $foo seconds to allow a control-z to suspend and debug" ; sleep $foo send "\003\r" ; sleep 1; send "\003\r" ; sleep 1; wait_for_shell_prompt ; echo_and_say "Got prompt" break } {.} { send_user "~" } } } } bitcask-2.1.0/test/multi_backend.schema0000644000232200023220000000306113655023466020454 0ustar debalancedebalance%% -*- erlang -*- %% Yo, this is just a test backend for bitcask's multi_backend schema. It's not real %% If you really care about multi_backend, have a look at riak_kv/priv/multi_backend.schema %% @doc Storage_backend specifies the Erlang module defining the storage %% mechanism that will be used on this node. {mapping, "multi_backend.$name.storage_backend", "riak_kv.multi_backend", [ {default, bitcask}, {datatype, {enum, [bitcask, leveldb, memory]}}, hidden ]}. {translation, "riak_kv.multi_backend", fun(Conf, Schema) -> %% group by $name into list, also cut the "multi_backend.$name" off every key BackendNames = cuttlefish_variable:fuzzy_matches(["multi_backend","$name","storage_backend"], Conf), %% for each in list, case statement on backend type Backends = [ begin BackendConfigName = ["multi_backend", Name], {BackendModule, BackendConfig} = case cuttlefish:conf_get(BackendConfigName ++ ["storage_backend"], Conf) of bitcask -> BackendConfigPrefix = BackendConfigName ++ ["bitcask"], SubConf = [ begin {Key -- BackendConfigName, Value} end || {Key, Value} <- cuttlefish_variable:filter_by_prefix(BackendConfigPrefix, Conf)], case cuttlefish_generator:map(Schema, SubConf) of BackendProplist -> {riak_kv_bitcask_backend, proplists:get_value(bitcask, BackendProplist)} end end, {list_to_binary(Name), BackendModule, BackendConfig} end || {"$name", Name} <- BackendNames], case Backends of [] -> throw(unset); _ -> Backends end end}. bitcask-2.1.0/test/bitcask_schema_tests.erl0000644000232200023220000002254013655023466021362 0ustar debalancedebalance-module(bitcask_schema_tests). -include_lib("eunit/include/eunit.hrl"). -compile([export_all, nowarn_export_all]). %% basic schema test will check to make sure that all defaults from the schema %% make it into the generated app.config basic_schema_test_() -> {timeout, 60, fun basic_schema_test2/0}. basic_schema_test2() -> lager:start(), %% The defaults are defined in ../priv/bitcask.schema. it is the file under test. Config = cuttlefish_unit:generate_templated_config("priv/bitcask.schema", [], context(), predefined_schema()), cuttlefish_unit:assert_config(Config, "bitcask.data_root", "./data/bitcask"), cuttlefish_unit:assert_config(Config, "bitcask.open_timeout", 4), cuttlefish_unit:assert_config(Config, "bitcask.sync_strategy", none), cuttlefish_unit:assert_config(Config, "bitcask.max_file_size", 2147483648), cuttlefish_unit:assert_config(Config, "bitcask.merge_window", always), cuttlefish_unit:assert_config(Config, "bitcask.frag_merge_trigger", 60), cuttlefish_unit:assert_config(Config, "bitcask.dead_bytes_merge_trigger", 536870912), cuttlefish_unit:assert_config(Config, "bitcask.frag_threshold", 40), cuttlefish_unit:assert_config(Config, "bitcask.dead_bytes_threshold", 134217728), cuttlefish_unit:assert_config(Config, "bitcask.small_file_threshold", 10485760), cuttlefish_unit:assert_config(Config, "bitcask.max_fold_age", -1), cuttlefish_unit:assert_config(Config, "bitcask.max_fold_puts", 0), cuttlefish_unit:assert_config(Config, "bitcask.expiry_secs", -1), cuttlefish_unit:assert_config(Config, "bitcask.require_hint_crc", true), cuttlefish_unit:assert_config(Config, "bitcask.expiry_grace_time", 0), cuttlefish_unit:assert_config(Config, "bitcask.io_mode", erlang), %% Make sure no multi_backend cuttlefish_unit:assert_not_configured(Config, "riak_kv.multi_backend"), ok. merge_window_test_() -> {timeout, 60, fun merge_window_test2/0}. merge_window_test2() -> lager:start(), Conf = [ {["bitcask", "merge", "policy"], window}, {["bitcask", "merge", "window", "start"], 0}, {["bitcask", "merge", "window", "end"], 12} ], %% The defaults are defined in ../priv/bitcask.schema. it is the file under test. Config = cuttlefish_unit:generate_templated_config("priv/bitcask.schema", Conf, context(), predefined_schema()), cuttlefish_unit:assert_config(Config, "bitcask.data_root", "./data/bitcask"), cuttlefish_unit:assert_config(Config, "bitcask.open_timeout", 4), cuttlefish_unit:assert_config(Config, "bitcask.sync_strategy", none), cuttlefish_unit:assert_config(Config, "bitcask.max_file_size", 2147483648), cuttlefish_unit:assert_config(Config, "bitcask.merge_window", {0, 12}), cuttlefish_unit:assert_config(Config, "bitcask.frag_merge_trigger", 60), cuttlefish_unit:assert_config(Config, "bitcask.dead_bytes_merge_trigger", 536870912), cuttlefish_unit:assert_config(Config, "bitcask.frag_threshold", 40), cuttlefish_unit:assert_config(Config, "bitcask.dead_bytes_threshold", 134217728), cuttlefish_unit:assert_config(Config, "bitcask.small_file_threshold", 10485760), cuttlefish_unit:assert_config(Config, "bitcask.max_fold_age", -1), cuttlefish_unit:assert_config(Config, "bitcask.max_fold_puts", 0), cuttlefish_unit:assert_config(Config, "bitcask.expiry_secs", -1), cuttlefish_unit:assert_config(Config, "bitcask.require_hint_crc", true), cuttlefish_unit:assert_config(Config, "bitcask.expiry_grace_time", 0), cuttlefish_unit:assert_config(Config, "bitcask.io_mode", erlang), %% Make sure no multi_backend cuttlefish_unit:assert_not_configured(Config, "riak_kv.multi_backend"), ok. override_schema_test_() -> {timeout, 60, fun override_schema_test2/0}. override_schema_test2() -> lager:start(), %% Conf represents the riak.conf file that would be read in by cuttlefish. %% this proplists is what would be output by the conf_parse module Conf = [ {["bitcask", "data_root"], "/absolute/data/bitcask"}, {["bitcask", "open_timeout"], 2}, {["bitcask", "sync", "strategy"], interval}, {["bitcask", "sync", "interval"], "10s"}, {["bitcask", "max_file_size"], "4GB"}, {["bitcask", "merge", "policy"], never}, {["bitcask", "merge", "window", "start"], 0}, {["bitcask", "merge", "window", "end"], 12}, {["bitcask", "merge", "triggers", "fragmentation"], 20}, {["bitcask", "merge", "triggers", "dead_bytes"], "256MB"}, {["bitcask", "merge", "thresholds", "fragmentation"], 10}, {["bitcask", "merge", "thresholds", "dead_bytes"], "64MB"}, {["bitcask", "merge", "thresholds", "small_file"], "5MB"}, {["bitcask", "fold", "max_age"], "12ms"}, {["bitcask", "fold", "max_puts"], 7}, {["bitcask", "expiry"], "20s" }, {["bitcask", "hintfile_checksums"], "allow_missing"}, {["bitcask", "expiry", "grace_time"], "15s" }, {["bitcask", "io_mode"], nif} ], %% The defaults are defined in ../priv/bitcask.schema. it is the file under test. Config = cuttlefish_unit:generate_templated_config("priv/bitcask.schema", Conf, context(), predefined_schema()), cuttlefish_unit:assert_config(Config, "bitcask.data_root", "/absolute/data/bitcask"), cuttlefish_unit:assert_config(Config, "bitcask.open_timeout", 2), cuttlefish_unit:assert_config(Config, "bitcask.sync_strategy", {seconds, 10}), cuttlefish_unit:assert_config(Config, "bitcask.max_file_size", 4294967296), cuttlefish_unit:assert_config(Config, "bitcask.merge_window", never), cuttlefish_unit:assert_config(Config, "bitcask.frag_merge_trigger", 20), cuttlefish_unit:assert_config(Config, "bitcask.dead_bytes_merge_trigger", 268435456), cuttlefish_unit:assert_config(Config, "bitcask.frag_threshold", 10), cuttlefish_unit:assert_config(Config, "bitcask.dead_bytes_threshold", 67108864), cuttlefish_unit:assert_config(Config, "bitcask.small_file_threshold", 5242880), cuttlefish_unit:assert_config(Config, "bitcask.max_fold_age", 12000), cuttlefish_unit:assert_config(Config, "bitcask.max_fold_puts", 7), cuttlefish_unit:assert_config(Config, "bitcask.expiry_secs", 20), cuttlefish_unit:assert_config(Config, "bitcask.require_hint_crc", false), cuttlefish_unit:assert_config(Config, "bitcask.expiry_grace_time", 15), cuttlefish_unit:assert_config(Config, "bitcask.io_mode", nif), %% Make sure no multi_backend cuttlefish_unit:assert_not_configured(Config, "riak_kv.multi_backend"), ok. multi_backend_test_() -> {timeout, 60, fun multi_backend_test2/0}. multi_backend_test2() -> Conf = [ {["multi_backend", "default", "storage_backend"], bitcask}, {["multi_backend", "default", "bitcask", "data_root"], "/data/default_bitcask"} ], %% The defaults are defined in ../priv/bitcask.schema. it is the file under test. Config = cuttlefish_unit:generate_templated_config( ["priv/bitcask.schema", "priv/bitcask_multi.schema", "test/multi_backend.schema"], Conf, context(), predefined_schema()), %%io:format("Config: ~p~n", []), MultiBackendConfig = proplists:get_value(multi_backend, proplists:get_value(riak_kv, Config)), {<<"default">>, riak_kv_bitcask_backend, DefaultBackend} = lists:keyfind(<<"default">>, 1, MultiBackendConfig), cuttlefish_unit:assert_config(DefaultBackend, "data_root", "/data/default_bitcask"), cuttlefish_unit:assert_config(DefaultBackend, "open_timeout", 4), cuttlefish_unit:assert_config(DefaultBackend, "sync_strategy", none), cuttlefish_unit:assert_config(DefaultBackend, "max_file_size", 2147483648), cuttlefish_unit:assert_config(DefaultBackend, "merge_window", always), cuttlefish_unit:assert_config(DefaultBackend, "frag_merge_trigger", 60), cuttlefish_unit:assert_config(DefaultBackend, "dead_bytes_merge_trigger", 536870912), cuttlefish_unit:assert_config(DefaultBackend, "frag_threshold", 40), cuttlefish_unit:assert_config(DefaultBackend, "dead_bytes_threshold", 134217728), cuttlefish_unit:assert_config(DefaultBackend, "small_file_threshold", 10485760), cuttlefish_unit:assert_config(DefaultBackend, "max_fold_age", -1), cuttlefish_unit:assert_config(DefaultBackend, "max_fold_puts", 0), cuttlefish_unit:assert_config(DefaultBackend, "expiry_secs", -1), cuttlefish_unit:assert_config(DefaultBackend, "require_hint_crc", true), cuttlefish_unit:assert_config(DefaultBackend, "expiry_grace_time", 0), cuttlefish_unit:assert_config(DefaultBackend, "io_mode", erlang), ok. %% this context() represents the substitution variables that rebar %% will use during the build process. riak_core's schema file is %% written with some {{mustache_vars}} for substitution during %% packaging cuttlefish doesn't have a great time parsing those, so we %% perform the substitutions first, because that's how it would work %% in real life. context() -> []. %% This predefined schema covers riak_kv's dependency on %% platform_data_dir predefined_schema() -> Mapping = cuttlefish_mapping:parse({mapping, "platform_data_dir", "riak_core.platform_data_dir", [ {default, "./data"}, {datatype, directory} ]}), {[], [Mapping], []}. bitcask-2.1.0/README.md0000644000232200023220000000055413655023466014775 0ustar debalancedebalance# Bitcask - A Log-Structured Hash Table for Fast Key/Value Data [![Build Status](https://secure.travis-ci.org/basho/bitcask.png?branch=master)](http://travis-ci.org/basho/bitcask) Bitcask uses the "rebar" build system, but we have provided a wrapper Makefile so that simply running "make" at the top level should work. Bitcask requires Erlang R14B04 or later. bitcask-2.1.0/eqc/0000755000232200023220000000000013655023466014262 5ustar debalancedebalancebitcask-2.1.0/eqc/event_logger.erl0000644000232200023220000001205113655023466017445 0ustar debalancedebalance%%% File : event_logger.erl %%% Author : Ulf Norell %%% Description : %%% Created : 26 Mar 2012 by Ulf Norell -module(event_logger). -compile([export_all, nowarn_export_all]). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -endif. -behaviour(gen_server). %% API -export([start_link/0, event/1, get_events/0, start_logging/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). -record(event, { timestamp :: integer(), data :: term() }). -record(state, { start_time :: integer(), events = [] :: [#event{}] }). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% Function: start_link() -> {ok,Pid} | ignore | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). start_logging() -> gen_server:call(?MODULE, {start, timestamp()}). event(EventData) -> gen_server:call(?MODULE, #event{ timestamp = timestamp(), data = EventData }). async_event(EventData) -> gen_server:cast(?MODULE, #event{ timestamp = timestamp(), data = EventData }). get_events() -> gen_server:call(?MODULE, get_events). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([]) -> {ok, #state{}}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> %% {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call(Event = #event{}, _From, State) -> {reply, ok, add_event(Event, State)}; handle_call({start, Now}, _From, S) -> {reply, ok, S#state{ events = [], start_time = Now }}; handle_call(get_events, _From, S) -> {reply, lists:reverse([ {E#event.timestamp, E#event.data} || E <- S#state.events]), S#state{ events = [] }}; handle_call(Request, _From, State) -> {reply, {error, {bad_call, Request}}, State}. %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast(Event = #event{}, State) -> {noreply, add_event(Event, State)}; handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info(_Info, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, _State) -> ok. %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- add_event(#event{timestamp = Now, data = Data}, State) -> Event = #event{ timestamp = Now - State#state.start_time, data = Data }, State#state{ events = [Event|State#state.events] }. timestamp() -> erlang:system_time(microsecond). bitcask-2.1.0/eqc/pulse/0000755000232200023220000000000013655023466015412 5ustar debalancedebalancebitcask-2.1.0/eqc/pulse/Emakefile0000644000232200023220000000062513655023466017222 0ustar debalancedebalance{"../test/token", [{parse_transform, eqc_cover}]}. {"../test/handle_errors", [{d, 'PULSE'}, {parse_transform, eqc_cover}]}. {"../src/*", [{d, 'PULSE'}, {parse_transform, eqc_cover}, {i, ".."}, {i, "../include"}, {d, namespaced_types}]}. {"../eqc/pulse/*", [{d, 'PULSE'}, {parse_transform, eqc_cover}, {i, ".."}, {i, "../include"}]}. {"../eqc/event_logger", [{d, 'PULSE'}, {i, ".."}, {i, "../include"}]}. bitcask-2.1.0/eqc/pulse/bitcask_pulse.erl0000644000232200023220000011721613655023466020756 0ustar debalancedebalance%%% File : bitcask_pulse.erl %%% Author : Ulf Norell, Hans Svensson %%% Description : Bitcask stress testing %%% Created : 19 Mar 2012 by Ulf Norell -module(bitcask_pulse). %% The while module is ifdef:ed, rebar should set PULSE -ifdef(PULSE). -compile([export_all, nowarn_export_all]). -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_statem.hrl"). -include("../include/bitcask.hrl"). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). %% The following functions contains side_effects but are run outside %% PULSE, i.e. PULSE needs to leave them alone -compile({pulse_skip,[{really_delete_bitcask, 0}, {copy_bitcask_app, 0}, {get_errors, 0}]}). -compile({pulse_no_side_effect,[{file,'_','_'}, {erlang, now, 0}]}). %% Each test uses a fresh directory! -define(BITCASK, token:get_name()). %% -define(BITCASK, filename:join(?TEST_FILEPATH, "bitcask.qc." ++ os:getpid())). %% -define(BITCASK, "bitcask.qc." ++ os:getpid()). %% Number of keys used in the tests -define(NUM_KEYS, 50). %% max_file_size given to bitcask. -define(FILE_SIZE, 400). %% Signal that Bitcask is under test -define(BITCASK_TESTING_KEY, bitcask_testing_module). %% Max number of forks to run simultaneously. Too many means huge pauses %% while PULSE & postcondition checking operates. -define(FORK_CONC_LIMIT, 2). %% Used for output within EUnit... -define(QC_FMT(Fmt, Args), io:format(user, Fmt, Args)). %% And to force EUnit to output QuickCheck output... -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> ?QC_FMT(Str, Args) end, P)). %% 2-tuple prefix for local process dictionary hackery -define(NAME_KEY, local_proc_hack). -record(state, { handle :: reference() | tuple() , is_writer = true :: boolean() , did_fork_merge = false :: boolean() , readers = [] :: list() }). %% The initial state. initial_state() -> #state{}. %% Generators key() -> choose(1, ?NUM_KEYS). value() -> ?LET(Bin, noshrink(binary()), ?SHRINK(Bin, [<< <<0>> || <<_>> <= Bin >>])). sleep_time() -> elements([200, 500, 1000]). key_pair() -> ?LET({A, B}, {key(), key()}, list_to_tuple(lists:sort([A, B]))). not_commands(Module, State) -> ?LET(Cmds, commands(Module, State), uncommand(Cmds)). gen_make_merge_file() -> noshrink( %% {true|false, Seed, Probability} {frequency([{100, false}, {109999, true}]), {choose(0, 500), choose(0, 500), choose(0, 500)}, %% weighting: use either 100% or 1-100% frequency([{2, 100}, {8, choose(1, 100)}]) }). %% Command generator. S is the state. command(S) -> frequency( [ {2, {call, ?MODULE, fork, [not_commands(?MODULE, #state{ is_writer = false })]}} || S#state.is_writer andalso length(S#state.readers) < ?FORK_CONC_LIMIT] ++ %% || S#state.is_writer ] ++ [ {3, {call, ?MODULE, incr_clock, []}} %% Any proc can call incr_clock ] ++ %% Move 'get' and 'put' methods to low frequency. 'fold' is the %% best for catching race bugs because it checks all keys instead of %% only one. 'puts' is much more effective also than 'put' because %% 'puts' operates on several keys at once. [ {10, {call, ?MODULE, gets, [S#state.handle, key_pair()]}} || S#state.handle /= undefined ] ++ [ {3, {call, ?MODULE, put, [S#state.handle, key(), value()]}} || S#state.is_writer, S#state.handle /= undefined ] ++ [ {10, {call, ?MODULE, puts, [S#state.handle, key_pair(), value()]}} || S#state.is_writer, S#state.handle /= undefined ] ++ %% Use a high rate for delete: we have a good chance of actually %% deleting something that was in a prior 'puts' range of keys. %% And we know that delete+merge has been a good source of %% race bugs, so keep doing it. [ {20, {call, ?MODULE, delete, [S#state.handle, key()]}} || S#state.is_writer, S#state.handle /= undefined ] ++ [ {2, {call, ?MODULE, bc_open, [S#state.is_writer, gen_make_merge_file()]}} || S#state.handle == undefined ] ++ [ {2, {call, ?MODULE, sync, [S#state.handle]}} || S#state.handle /= undefined ] ++ [ {1, {call, ?MODULE, fold_keys, [S#state.handle]}} || S#state.handle /= undefined ] ++ [ {7, {call, ?MODULE, fold, [S#state.handle]}} || S#state.handle /= undefined ] ++ %% We want to call close quite frequently, because historically %% there has been problems with delete/tombstones that only appear %% after closing & opening. [ {10, {call, ?MODULE, bc_close, [S#state.handle]}} || S#state.handle /= undefined ] ++ [ {12, {call, ?MODULE, merge, [S#state.handle]}} || S#state.is_writer, not S#state.did_fork_merge, S#state.handle /= undefined ] ++ [ {1, {call, ?MODULE, fork_merge, [S#state.handle]}} || S#state.is_writer, S#state.handle /= undefined ] ++ %% Disable 'join_reader' for now. [ {0, {call, ?MODULE, join_reader, [elements(S#state.readers)]}} || S#state.is_writer, S#state.readers /= []] ++ %% Disable 'kill' for now: this model has a flaw and needs some %% fixing to avoid false positives. %% TODO: fix it. :-) [ {0, {call, ?MODULE, kill, [elements([bitcask_merge_worker|S#state.readers])]}} || S#state.is_writer, S#state.handle /= undefined ] ++ [ {2, {call, ?MODULE, needs_merge, [S#state.handle]}} || S#state.is_writer, S#state.handle /= undefined ] ++ [ {1, {call, ?MODULE, sleep, [sleep_time()]}} ] ++ []). %% Precondition, checked before a command is added to the command sequence. precondition(S, {call, _, fork, _}) -> length(S#state.readers) < ?FORK_CONC_LIMIT andalso S#state.is_writer; precondition(_S, {call, _, incr_clock, _}) -> true; precondition(S, {call, _, gets, [H, _]}) -> S#state.handle == H; precondition(S, {call, _, get, [H, _]}) -> S#state.handle == H; precondition(S, {call, _, puts, [H, _, _]}) -> S#state.is_writer andalso S#state.handle == H; precondition(S, {call, _, put, [H, _, _]}) -> S#state.is_writer andalso S#state.handle == H; precondition(S, {call, _, delete, [H, _]}) -> S#state.is_writer andalso S#state.handle == H; precondition(S, {call, _, fork_merge, [H]}) -> S#state.is_writer andalso S#state.handle == H; precondition(S, {call, _, merge, [H]}) -> S#state.is_writer andalso not S#state.did_fork_merge andalso S#state.handle == H; precondition(S, {call, _, needs_merge, [H]}) -> S#state.is_writer andalso S#state.handle == H; precondition(S, {call, _, join_reader, [R]}) -> S#state.is_writer andalso lists:member(R, S#state.readers); precondition(S, {call, _, fold_keys, [H]}) -> S#state.handle == H; precondition(S, {call, _, fold, [H]}) -> S#state.handle == H; precondition(S, {call, _, sync, [H]}) -> S#state.handle == H; precondition(S, {call, _, kill, [Pid]}) -> S#state.is_writer andalso S#state.handle /= undefined andalso (Pid == bitcask_merge_worker orelse lists:member(Pid, S#state.readers)); precondition(S, {call, _, bc_close, [H]}) -> S#state.handle == H; precondition(S, {call, _, bc_open, [Writer, _MakeMergeFile]}) -> %% The writer can open for reading but not the other way around. S#state.is_writer >= Writer andalso S#state.handle == undefined; precondition(_, {call, _, sleep, _}) -> true. %% Next state transformation, S is the current state and V is the result of the %% command. next_state(S, H, {call, _, bc_open, _}) -> S#state{ handle = H }; next_state(S, _, {call, _, bc_close, _}) -> S#state{ handle = undefined }; next_state(S, _, {call, _, join_reader, [R]}) -> S#state{ readers = S#state.readers -- [R] }; next_state(S, _, {call, _, fork_merge, _}) -> S#state{ did_fork_merge = true }; next_state(S, P, {call, _, fork, _}) -> S#state{ readers = [ P | S#state.readers ] }; next_state(S, _, {call, _, kill, [Pid]}) -> S#state{ readers = S#state.readers -- [Pid] }; next_state(S, _V, {call, _, _, _}) -> S. leq(X, X) -> true; leq(X, Y) -> {X, '/=', Y}. %% Postcondition, checked after the command has been evaluated. V is the result %% of the command. Note: S is the state before next_state/3 has been called. postcondition(_S, {call, _, put, _}, V) -> leq(V, ok); postcondition(_S, {call, _, puts, _}, V) -> leq(V, ok); postcondition(_S, {call, _, delete, _}, V) -> leq(V, ok); postcondition(_S, {call, _, fork_merge, _}, V) -> case V of ok -> true; not_needed -> true; already_queued -> true; %% another fork_merge might be in progress {'EXIT', {killed, _}} -> true; {'EXIT', {noproc, _}} -> true; _ -> {fork_merge, V} end; postcondition(_S, {call, _, merge, _}, V) -> case V of ok -> true; not_needed -> true; {error, {merge_locked, locked, _}} -> true; %% a fork_merge might be in progress _ -> {merge, V} end; postcondition(_S, {call, _, join_reader, _}, V) -> leq(V, ok); postcondition(_S, {call, _, fold, _}, V) -> case V of {error, max_retries_exceeded_for_fold} -> true; _ when is_list(V) -> true; _ -> {fold, V} end; postcondition(_S, {call, _, fold_keys, _}, V) -> case V of {error, _} -> V; _ -> true end; postcondition(_S, {call, _, needs_merge, _}, V) -> case V of {true, _Files} -> true; false -> true; _ -> {needs_merge, V} end; postcondition(_S, {call, _, bc_open, [IsWriter, _MakeMergeFile]}, V) -> case V of _ when is_reference(V) andalso IsWriter -> true; %% check_no_tombstones(V, true); _ when is_reference(V) -> true; {'EXIT', {{badmatch,{error,enoent}},_}} -> true; %% If we manage to get a timeout, there's a pathological scheduling %% delay that is causing starvation. Expose the starvation by %% not considering {error, timeout} a success. {error, timeout} -> {dont_want_this_timeout, V}; _ -> {bc_open, V} end; postcondition(_S, {call, _, gets, _}, Vs) -> case [X || X <- Vs, X /= not_found andalso not (is_tuple(X) andalso tuple_size(X) == 2 andalso element(1, X) == ok andalso is_binary(element(2, X)))] of [] -> true; _ -> {gets, Vs} end; postcondition(_S, {call, _, _, _}, _V) -> true. %% Slave node preparation %% %% Each test is run on a freshly started node, to avoid problems with %% cleanup, etc. node_name() -> list_to_atom(lists:concat([slave_name(), "@", host()])). slave_name() -> list_to_atom(lists:concat([hd(string:tokens(atom_to_list(node()),"@")), "-tester"])). host() -> hd(tl(string:tokens(atom_to_list(node()),"@"))). %% Generate a most likely unique node name unique_name() -> {A, B, C} = erlang:now(), list_to_atom(lists:concat([integer_to_list(A), "-", integer_to_list(B), "-", integer_to_list(C)])). %% Note, rebar starts a shell without distribution, possibly start %% net_kernel here start_node(Verbose) -> case node() of 'nonode@nohost' -> net_kernel:start([unique_name(), shortnames]); _ -> ok end, stop_node(), {ok, _} = slave:start(host(), slave_name(), "-pa ./.eunit -pz ./ebin ./deps/*/ebin " ++ lists:append(["-detached" || not Verbose ])), ok. stop_node() -> slave:stop(node_name()). run_on_node(local, _Verbose, M, F, A) -> apply(M, F, A); run_on_node(slave, Verbose, M, F, A) -> start_node(Verbose), rpc:call(node_name(), M, F, A, 45*60*1000). %% Muting the QuickCheck license printout from the slave node mute(true, Fun) -> Fun(); mute(false, Fun) -> Fun(). % mute:run(Fun). %% %% The actual code of the property, run on remote node via rpc:call above %% run_commands_on_node(LocalOrSlave, Cmds, Seed, Verbose, KeepFiles) -> mute(Verbose, fun() -> AfterTime = if LocalOrSlave == local -> 50000; LocalOrSlave == slave -> 1000000 end, pulse:start(), error_logger:tty(false), error_logger:add_report_handler(handle_errors), token:next_name(), X = try {H, S, Res, PidRs, Trace} = pulse:run(fun() -> event_logger:start_link(), event_logger:start_logging(), application_controller:start({application, kernel, []}), application:start(bitcask), bitcask_time:test__set_fudge(10), receive after AfterTime -> ok end, OldVerbose = pulse:verbose([ all || Verbose ]), {H, S, R} = run_commands(?MODULE, Cmds), Pids = lists:usort(S#state.readers), PidRs = fork_results(Pids), receive after AfterTime -> ok end, pulse:verbose(OldVerbose), Trace = event_logger:get_events(), receive after AfterTime -> ok end, application:stop(bitcask), unlink(whereis(pulse_application_controller)), exit(pulse_application_controller, shutdown), receive after AfterTime -> ok end, {H, S, R, PidRs, Trace} end, [{seed, Seed}, {strategy, unfair}]), Schedule = pulse:get_schedule(), Errors = get_errors(), {H, S, Res, PidRs, Trace, Schedule, Errors} catch _:Err -> {'EXIT', Err} end, case KeepFiles of false -> really_delete_bitcask(); true -> ok end, X end). get_errors() -> gen_event:call(error_logger, handle_errors, get_errors, 60*1000). run_tests(Args) -> try list_to_integer(atom_to_list(hd(Args))) of N -> case quickcheck(eqc:testing_time(N, prop_pulse())) of true -> erlang:halt(); false -> erlang:halt(1) end catch _:_ -> io:format("Bad arguments: ~p\n", [Args]), timer:sleep(10), erlang:halt(2) end. prop_pulse() -> prop_pulse(local, false, false). prop_pulse(Boolean) -> prop_pulse(local, Boolean, false). prop_pulse(LocalOrSlave, Verbose0, KeepFiles) -> ?LET(Verbose, parameter(verbose, Verbose0), ?FORALL(Cmds, commands(?MODULE), ?IMPLIES(length(Cmds) > 0, ?LET(Shrinking, parameter(shrinking, false), ?ALWAYS(if Shrinking -> 5; % re-do this many times in shrinking runs true -> 1 % re-do this many times in normal runs end, ?FORALL(Seed, pulse:seed(), begin case run_on_node(LocalOrSlave, Verbose, ?MODULE, run_commands_on_node, [LocalOrSlave, Cmds, Seed, Verbose, KeepFiles]) of {'EXIT', _} = Err -> equals(Err, ok); {badrpc, {'EXIT', _}} = Err -> equals(Err, ok); {badrpc, timeout} = Bad -> io:format(user, "GOT ~p, aborting. Stop PULSE and restart!\n", [Bad]), exit({stopping, Bad}); {H, S, Res, PidRs, Trace, Schedule, Errors0} -> Errors = [E || is_list(Errors0), E <- Errors0, re:run(element(2, E), "Invalid merge input") == nomatch] ++ [Errors0 || not is_list(Errors0)], ?WHENFAIL( ?QC_FMT("\nState: ~p\n", [S]), aggregate(zipwith(fun command_data/2, tl(Cmds), H), measure(len_cmds, length(Cmds), measure(deep_len_cmds, lists:foldl( fun({set,_,{call, _, fork, [L]}}, Acc) -> Acc + length(L); (_, Acc) -> Acc + 1 end, 0, Cmds), measure(schedule, length(Schedule), %% In the end we check four things: %% - That the root process (the writer) returns ok (passes all postconditions) %% - That all forked processes returns ok %% - That there are no unmasked errors in the error_log %% - That all read actions (gets, fold, and fold_keys) return possible values conjunction( [ {0, equals(Res, ok)} | [ {Pid, equals(R, ok)} || {Pid, R} <- PidRs ] ] ++ [ {errors, equals(Errors, [])} , {events, check_trace(Trace)} ])))))) end end)))))). %% Using eqc_temporal to keep track of possible values for keys. %% %% The Trace contains entries for start and finish of operations. For %% put(Key, Val) (and delete) we consider Key to have either the old %% or the new value until the operation has finished. Likewise, a get %% (or a fold/fold_keys) may see any of the potential values if the %% operation overlaps with a put/delete. check_trace(Trace) -> %% Turn the trace into a temporal relation Events0 = eqc_temporal:from_timed_list(Trace), %% convert pids and refs into something the reader won't choke on, %% and is easier to read, for ease of debugging the test. Events = clean_events(Events0), %% Clean up the process dictionary from clean_events()to avoid %% leaking memory on very long runs. [erase(K) || {K = {?NAME_KEY, _}, _} <- get()], %% The Calls relation contains {call, Pid, Call} whenever Call by Pid is in %% progress. Calls = eqc_temporal:stateful( fun({call, Pid, Call}) -> [{call, Pid, Call}] end, fun({call, Pid, _Call}, {result, Pid, _}) -> [] end, Events), %% Folds = eqc_temporal:stateful( %% fun({call, Pid, {fold, _}}) -> [{folding, Pid}]; %% ({call, Pid, {fold_keys, _}}) -> [{folding, Pid}] %% end, %% fun({folding, Pid}, {result, Pid, _}) -> [] end, %% Events), %% The initial value for each key is not_found. AllKeys = lists:usort(fold( fun({put, _, K, _}) -> K; ({get, _, K}) -> K; ({delete, _, K}) -> K end, Trace)), InitialPuts = eqc_temporal:elems(eqc_temporal:ret([{K, not_found} || K <- AllKeys])), %% For each put or delete the Puts relation contains the appropriate key/value pair. Puts = eqc_temporal:union(InitialPuts, eqc_temporal:map(fun({call, _Pid, {put, _H, K, V}}) -> {K, V}; ({call, _Pid, {delete, _H, K}}) -> {K, not_found} end, Calls)), %% DonePut contains {freeze, K, V} when a put or delete has completed. DonePut = eqc_temporal:map( fun({K, V}) -> {freeze, K, V} end, eqc_temporal:subtract(eqc_temporal:shift(1, Puts), Puts)), %% Values contains {K, V} whenever V is a possible value for K. We compute it %% by adding {K, V} whenever it appears in Puts and removing any {K, V1} as %% soon as we see {freeze, K, V2} (with V1 /= V2) in DonePut. In effect, when %% a put/delete call is started both the old and the new value appears in %% Values and when the call has completed only the new value does. Values = eqc_temporal:stateful( fun({K, V}) -> {K, V} end, fun({K, V1}, {freeze, K, V2}) when V1 /= V2 -> [] end, eqc_temporal:union(Puts, DonePut)), %% Build an orddict for key/values for efficiency reasons. ValueDict = eqc_temporal:map( fun(KVs) -> lists:foldr(fun({K,V}, Dict) -> orddict:append(K, V, Dict) end, orddict:new(), KVs) end, eqc_temporal:set(Values)), %% The Reads relation keeps track of get, fold_keys and fold calls with the %% purpose of checking that they return something sensible. For a get call we %% check that the result was a possible value at some point during the %% evaluation of the call. The fold and fold_keys are checked analogously. Reads = eqc_temporal:stateful( %% Starting a read call. For get we aggregate the list of possible values %% for the corresponding key seen during the call. For fold we keep a %% dictionary mapping keys to lists of possible values and for fold_keys %% we keep a dictionary mapping keys to a list of found or not_found. fun({call, Pid, {get, _H, K}}) -> {get, Pid, K, []}; ({call, Pid, {fold_keys, _H}}) -> {fold_keys, Pid, orddict:new()}; ({call, Pid, {fold, _H}}) -> {fold, Pid, orddict:new()} end, fun %% Update a get call with a new set of possible values. ({get, Pid, K, Vs}, {values, Vals}) -> %% Get all possible values for K Ws = case orddict:find(K, Vals) of error -> []; {ok, Us} -> lists:sort(Us) end, %% Add them to the previously seen values VWs = lists:umerge(Vs, Ws), false = VWs == Vs, %% Be careful not to update the state if nothing %% changed since eqc_temporal:stateful allows you %% to change the state several times during the %% same time slot. [{get, Pid, K, VWs}]; %% Update a fold_keys call ({fold_keys, Pid, Vals1}, {values, Vals2}) -> %% We don't need the actual values for fold_keys, just found or %% not_found. FoundOrNotFound = fun(not_found) -> not_found; (_) -> found end, %% Merge the new values and the old values Vals = orddict:merge( fun(_, Vs1, Vs2) -> lists:umerge(Vs1, Vs2) end, Vals1, orddict:map(fun(_, Vs) -> lists:usort(lists:map(FoundOrNotFound, Vs)) end, Vals2)), %% Make sure to not do any identity updates false = orddict:to_list(Vals) == orddict:to_list(Vals1), [{fold_keys, Pid, Vals}]; %% Update a fold call ({fold, Pid, Vals1}, {values, Vals2}) -> Vals = orddict:merge( fun(_, Vs1, Vs2) -> lists:umerge(Vs1, lists:sort(Vs2)) end, Vals1, Vals2), false = orddict:to_list(Vals) == orddict:to_list(Vals1), [{fold, Pid, Vals}]; %% Check a call to get ({get, Pid, K, Vs}, {result, Pid, R}) -> V = case R of not_found -> not_found; {ok, U} -> U end, case lists:member(V, Vs) of true -> []; %% V is a good result false -> [{bad, Pid, {get, K, Vs, V}}] %% V is a bad result! end; %% Check a call to fold_keys ({fold_keys, Pid, Vals}, {result, Pid, Keys}) -> %% check_fold_keys_result checks that %% K in Keys ==> found in Vals[K] and %% K not in Keys ==> not_found in Vals[K] case check_fold_keys_result(orddict:to_list(Vals), lists:sort(Keys)) of true -> []; false -> [{bad, Pid, {fold_keys, orddict:to_list(Vals), lists:sort(Keys)}}] end; %% Check a call to fold ({fold, Pid, Vals}, {result, Pid, KVs}) -> %% check_fold_result checks that %% {K, V} in KVs ==> V in Vals[K] and %% K not in KVs ==> not_found in Vals[K] case check_fold_result(orddict:to_list(Vals), lists:sort(KVs)) of true -> []; false -> [{bad, Pid, {fold, orddict:to_list(Vals), lists:sort(KVs)}}] end end, eqc_temporal:union(Events, eqc_temporal:map(fun(D) -> {values, D} end, ValueDict))), %% Filter out the bad stuff from the Reads relation. Bad = eqc_temporal:map(fun(X={bad, _, _}) -> X end, Reads), ?WHENFAIL( begin ?QC_FMT("Time: ~p ~p\n", [date(), time()]), ?QC_FMT("Events:\n~p\n", [Events]), ?QC_FMT("Bad:\n~p\n", [Bad]) end, eqc_temporal:is_false(Bad)). clean_events(Events) -> lists:map(fun pprint_ref_pid/1, Events). pprint_ref_pid({Time1, Time2, Events}) -> {Time1, Time2, recursive_clean(Events)}. recursive_clean([E|Rest]) -> [recursive_clean(E)|recursive_clean(Rest)]; recursive_clean(E) when is_tuple(E) -> L = tuple_to_list(E), list_to_tuple([recursive_clean(TE) || TE <- L]); recursive_clean(E) when is_pid(E) -> get_name(E, pid); recursive_clean(E) when is_reference(E) -> get_name(E, ref); recursive_clean(E) -> E. get_name(E, Base) -> case get({?NAME_KEY, E}) of undefined -> Name = fresh_name(Base), put({?NAME_KEY, E}, Name), Name; Name -> Name end. fresh_name(Base) -> Num = case get({?NAME_KEY, Base}) of undefined -> put({?NAME_KEY ,Base}, 1), 1; N -> put({?NAME_KEY, Base}, N+1), N+1 end, list_to_binary(atom_to_list(Base)++"_"++integer_to_list(Num)). check_fold_result([{K, Vs}|Expected], [{K, V}|Actual]) -> lists:member(V, Vs) andalso check_fold_result(Expected, Actual); check_fold_result([{_, Vs}|Expected], Actual) -> lists:member(not_found, Vs) andalso check_fold_result(Expected, Actual); check_fold_result([], []) -> true. check_fold_keys_result([{K, Vs}|Expected], [K|Actual]) -> lists:member(found, Vs) andalso check_fold_keys_result(Expected, Actual); check_fold_keys_result([{_, Vs}|Expected], Actual) -> lists:member(not_found, Vs) andalso check_fold_keys_result(Expected, Actual); check_fold_keys_result([], []) -> true. %% Presenting command data statistics in a nicer way command_data({set, _, {call, _, merge, _}}, H) -> case eqc_statem:history_result(H) of {error, {merge_locked, _, _}} -> {merge, locked}; {normal, V} -> {merge, V} end; command_data({set, _, {call, _, fork_merge, _}}, H) -> case eqc_statem:history_result(H) of {'EXIT', _} -> {fork_merge, 'EXIT'}; {normal, V} -> {fork_merge, V} end; command_data({set, _, {call, _, bc_open, _}}, H) -> case eqc_statem:history_result(H) of {'EXIT', _} -> {bc_open, 'EXIT'}; {error, Err} -> {bc_open, Err}; {normal, Ref} when is_reference(Ref) -> bc_open end; command_data({set, _, {call, _, needs_merge, _}}, H) -> case eqc_statem:history_result(H) of {normal, {true, _}} -> {needs_merge, true}; {normal, false} -> {needs_merge, false}; {normal, Other} -> {needs_merge, Other} end; command_data({set, _, {call, _, kill, [Pid]}}, _H) -> case Pid of bitcask_merge_worker -> {kill, merger}; _ -> {kill, reader} end; command_data({set, _, {call, _, Fun, _}}, _H) -> Fun. %% Wait for all forks to return their results fork_results(Pids) -> [ receive {Pid, done, R} -> {I, R} after 99999*1000 -> {I, timeout_fork_results} end || {I, Pid} <- lists:zip(lists:seq(1, length(Pids)), Pids) ]. %% Implementation of a the commands -define(CHECK_HANDLE(H, X, Do), case H of _ when is_reference(H) -> Do; _ -> X end). -define(LOG(Tag, MkCall), pulse:event(Tag), event_logger:event({call, self(), Tag}), __Result = MkCall, pulse:event({Tag, '->', __Result}), event_logger:event({result, self(), __Result}), __Result). fork(Cmds) -> ?LOG(fork, begin Mama = self(), spawn(fun() -> {_, S, R} = run_commands(?MODULE, recommand(Cmds)), bc_close(S#state.handle), Mama ! {self(), done, R} end) end). incr_clock() -> ?LOG(incr_clock, bitcask_time:test__incr_fudge(1)). nice_key(K) -> list_to_binary(io_lib:format("kk~2.2.0w", [K])). un_nice_key(<<"kk", Num:2/binary>>) -> list_to_integer(binary_to_list(Num)). get(H, K) -> ?LOG({get, H, K}, ?CHECK_HANDLE(H, not_found, bitcask:get(H, nice_key(K)))). gets(H, {Start, End}) -> [get(H, K) || K <- lists:seq(Start, End)]. put(H, K, V) -> ?LOG({put, H, K, V}, ?CHECK_HANDLE(H, ok, bitcask:put(H, nice_key(K), V))). puts(H, {K1, K2}, V) -> case lists:usort([ put(H, K, V) || K <- lists:seq(K1, K2) ]) of [ok] -> ok; Other -> Other end. delete(H, K) -> ?LOG({delete, H, K}, ?CHECK_HANDLE(H, ok, bitcask:delete(H, nice_key(K)))). fork_merge(H) -> ?LOG({fork_merge, H}, ?CHECK_HANDLE(H, not_needed, case needs_merge_wrapper(H) of {true, Files} -> catch bitcask_merge_worker:merge(?BITCASK, [], Files); false -> not_needed; Else -> Else end)). merge(H) -> ?LOG({merge,H}, ?CHECK_HANDLE(H, not_needed, case needs_merge_wrapper(H) of {true, Files} -> case catch bitcask:merge(?BITCASK, [], Files) of {'EXIT', Err} -> Err; R -> R end; false -> not_needed end)). kill(Pid) -> ?LOG({kill, Pid}, (catch exit(Pid, kill))). needs_merge(H) -> ?LOG({needs_merge, H}, ?CHECK_HANDLE(H, false, needs_merge_wrapper(H))). needs_merge_wrapper(H) -> case ok of %% check_no_tombstones(H, ok) of ok -> bitcask:needs_merge(H); Else -> {needs_merge_wrapper_error, Else} end. join_reader(ReaderPid) -> receive {ReaderPid, done, Res} -> Res end. sync(H) -> ?LOG({sync, H}, ?CHECK_HANDLE(H, ok, bitcask:sync(H))). fold(H) -> ?LOG({fold, H}, ?CHECK_HANDLE(H, [], bitcask:fold(H, fun(Kb, V, Acc) -> [{un_nice_key(Kb),V}|Acc] end, []))). fold_keys(H) -> ?LOG({fold_keys, H}, ?CHECK_HANDLE(H, [], bitcask:fold_keys(H, fun(#bitcask_entry{key = Kb}, Ks) -> [un_nice_key(Kb)|Ks] end, []))). bc_open(Writer, {MakeMergeFileP, Seed, Probability}) -> erlang:put(?BITCASK_TESTING_KEY, ?MODULE), if MakeMergeFileP -> make_merge_file(?BITCASK, Seed, Probability); true -> ok end, ?LOG({open, Writer}, case Writer of true -> catch bitcask:open(?BITCASK, [read_write, {max_file_size, ?FILE_SIZE}, {open_timeout, 1234}]); false -> catch bitcask:open(?BITCASK, [{open_timeout, 1234}]) end). make_merge_file(Dir, Seed, Probability) -> rand:seed(exrop, Seed), case filelib:is_dir(Dir) of true -> DataFiles = filelib:wildcard("*.data", Dir), {ok, FH} = file:open(Dir ++ "/merge.txt", [write]), [case rand:uniform(100) < Probability of true -> io:format(FH, "~s\n", [DF]); false -> ok end || DF <- DataFiles], ok = file:close(FH); false -> ok end. bc_close(H) -> ?LOG({close, H}, ?CHECK_HANDLE(H, ok, bitcask:close(H))). sleep(N) -> timer:sleep(N). %% Convenience functions for running tests test() -> test({20, sec}). test(N) when is_integer(N) -> quickcheck(numtests(N, prop_pulse())); test({Time, sec}) -> quickcheck(eqc:testing_time(Time, prop_pulse())); test({Time, min}) -> test({Time * 60, sec}); test({Time, h}) -> test({Time * 60, min}). check() -> check(current_counterexample()). verbose() -> verbose(current_counterexample()). verbose(CE) -> erlang:put(verbose, true), Ok = check(CE), erlang:put(verbose, false), Ok. check(CE) -> check(on_output(fun("OK" ++ _, []) -> ok; (Fmt, Args) -> io:format(Fmt, Args) end, prop_pulse(true == erlang:get(verbose))), mk_counterexample(CE)). recheck() -> recheck(prop_pulse()). %% Custom shrinking %% %% Applied after normal shrinking or manually via custom_shrink/1 shrink_commands(Cmds) -> ?LET(ok, ?SHRINK(ok, begin io:format("|"), [] end), shrink_commands1(Cmds)). shrink_commands1(Cmds) -> ?SHRINK(Cmds, lists:map(fun shrink_commands1/1, shrink(Cmds))). check_preconditions([{init, S}|Cmds]) -> check_preconditions(S, Cmds); check_preconditions(Cmds) -> check_preconditions(initial_state(), Cmds). check_preconditions(_S, []) -> true; check_preconditions(S, [{set, X, Call}|Cmds]) -> precondition(S, Call) andalso check_preconditions(next_state(S, X, Call), Cmds). shrink(Cmds) -> [ C || C <- shrink1(Cmds), check_preconditions(C) ]. shrink_nat(0) -> []; shrink_nat(1) -> [0]; shrink_nat(N) -> [0, N div 2, N - 1]. shrink_pos(N) -> [ M + 1 || M <- shrink_nat(N - 1) ]. shrink_bounds({A, B}) -> [ {A, A + Delta} || Delta <- shrink_nat(B - A) ] ++ [ {B - Delta, B} || Delta <- shrink_nat(B - A) ] ++ [ {Delta, B - A + Delta} || Delta <- shrink_pos(A) ]. shrink1(Cmds) -> %% Shrink puts to put (identity) shrink_sublist(1, Cmds, fun ([{set, X, {call, ?MODULE, puts, [H, {K, K}, V]}}]) -> [ [{set, X, {call, ?MODULE, put, [H, K, V]}}] ]; (_) -> [] end) ++ %% Move forks as late as possible shrink_tails(Cmds, fun ([Cmd={set, _, {call, _, fork, _}} | Cmds_ ]) -> [ Cmds0 ++ [Cmd] ++ Cmds1 || I <- lists:reverse(lists:seq(1, length(Cmds_))), {Cmds0, Cmds1} <- [lists:split(I, Cmds_)], lists:any(fun({set, _, {call, _, Fun, _}}) -> Fun /= fork end, Cmds0) ]; (_) -> [] end) ++ %% Merge two puts into one shrink_sublist(2, Cmds, fun ([{set, X, {call, ?MODULE, puts, [H, {K1, K2}, V1]}}, {set, _, {call, ?MODULE, puts, [H, {K3, K4}, V2]}}]) -> V = max_binary(V1, V2), [[{set, X, {call, ?MODULE, puts, [H, {K1, K2 + K4 - K3 + 1}, V]}}]]; ([{set, X, {call, ?MODULE, puts, [H, {K1, K2}, V1]}}, {set, _, {call, ?MODULE, put, [H, _K, V2]}}]) -> V = max_binary(V1, V2), [[{set, X, {call, ?MODULE, puts, [H, {K1, K2 + 1}, V]}}]]; ([{set, X, {call, ?MODULE, put, [H, K, V1]}}, {set, _, {call, ?MODULE, puts, [H, {K3, K4}, V2]}}]) -> V = max_binary(V1, V2), [[{set, X, {call, ?MODULE, puts, [H, {K, K + K4 - K3 + 1}, V]}}]]; ([{set, X, {call, ?MODULE, put, [H, K, V1]}}, {set, _, {call, ?MODULE, put, [H, _, V2]}}]) -> V = max_binary(V1, V2), [[{set, X, {call, ?MODULE, puts, [H, {K, K + 1}, V]}}]]; (_) -> [] end) ++ %% Shrink values of put shrink_sublist(1, Cmds, fun ([{set, X, {call, ?MODULE, puts, [H, {K1, K2}, <<_, V/binary>>]}}]) -> [[{set, X, {call, ?MODULE, puts, [H, {K1, K2}, V]}}]]; ([{set, X, {call, ?MODULE, put, [H, K, <<_, V/binary>>]}}]) -> [[{set, X, {call, ?MODULE, put, [H, K, V]}}]]; (_) -> [] end) ++ %% Shrink bounds of puts shrink_sublist(1, Cmds, fun ([{set, X, {call, ?MODULE, puts, [H, {K1, K2}, V]}}]) -> [[{set, X, {call, ?MODULE, puts, [H, {K3, K4}, V]}}] || {K3, K4} <- shrink_bounds({K1, K2}) ]; (_) -> [] end) ++ %% Inline a fork shrink_sublist(1, Cmds, fun ([{set, _, {call, ?MODULE, fork, [[{init, _}, {set, {not_var, H0}, {not_call, _, bc_open, _}} | NotCmds]]}}]) -> NextVar = 1 + lists:max([ X || {set, {var, X}, _} <- Cmds ]), YesCmds = rename_from(NextVar, recommand(NotCmds)), [ map(fun({var, H1}) when H1 == H0 -> H end, YesCmds) || H <- fold(fun({set, H, {call, _, bc_open, _}}) -> H end, Cmds) ]; (_) -> [] end) ++ %% shrink_tails(Cmds, fun %% ([{set, _, {call, ?MODULE, bc_close, [H1]}}, %% {set, H2, {call, ?MODULE, bc_open, _}} | Cmds1]) -> %% [map(fun(H3={var, _}) when H3 == H2 -> H1 end, Cmds1)]; %% (_) -> [] end) ++ shrink_sublist(1, Cmds, fun %% Shrink the commands of a fork ([{set, X, {call, ?MODULE, fork, [[Init={init, _}|NotCmds]]}}]) -> [ [{set, X, {call, ?MODULE, fork, [[Init|uncommand(Cmds1)]]}}] || Cmds1 <- shrink(recommand(NotCmds)) ]; (_) -> [] end) ++ %% Remove a single command shrink_sublist(1, Cmds, fun([_]) -> [[]] end) ++ []. max_binary(Bin1, Bin2) when size(Bin1) > size(Bin2) -> Bin1; max_binary(_Bin1, Bin2) -> Bin2. shrink_sublist(N, Xs, Shrink) -> shrink_sublist(N, [], Xs, [], Shrink). shrink_sublist(N, _Pre, Xs, Shrunk, _Shrink) when length(Xs) < N -> lists:append(lists:reverse(Shrunk)); shrink_sublist(N, Pre, Xs0 = [X|Xs], Shrunk, Shrink) -> {Ys, Zs} = lists:split(N, Xs0), NewShrunk = [ lists:reverse(Pre) ++ Shr ++ Zs || Shr <- Shrink(Ys) ], shrink_sublist(N, [X|Pre], Xs, [NewShrunk|Shrunk], Shrink). shrink_tails(Xs, Shrink) -> [ Ys ++ Ws || I <- lists:seq(0, length(Xs)), {Ys, Zs} <- [lists:split(I, Xs)], Ws <- Shrink(Zs) ]. custom_shrink() -> {ok, [Cmds]} = file:consult("shrunk"), custom_shrink(Cmds). custom_shrink(CE) -> custom_shrink(CE, 1). custom_shrink(CE0, Repeat) -> CE=[Cmds|_] = mk_counterexample(CE0), io:format("~p commands\n", [length(Cmds)]), Shrinkings = [ Cmds1 || Cmds1 <- shrink(Cmds) ], io:format("~p possible custom_shrinking steps\n", [length(Shrinkings)]), custom_shrink(CE, Shrinkings, Repeat). custom_shrink(CE, [], _) -> io:format("\n"), CE; custom_shrink(CE=[_,Seed|_], [C|Cs], Repeat) -> case check_many(Seed, C, Repeat) of true -> io:format("."), custom_shrink(CE, Cs, Repeat); Fail -> io:format("\n"), file:write_file("shrunk", io_lib:format("~p.\n", [Fail])), custom_shrink(Fail, Repeat) end. check_many(C, N) -> check_many(erlang:now(), C, N). check_many(_, _, 0) -> true; check_many(Seed, C0, N) -> C = mk_counterexample(C0, Seed), case check(C) of true -> check_many(C0, N - 1); false -> C end. mk_counterexample(CE = [Cmds, _Seed, Conj]) when is_list(Cmds) andalso is_list(Conj) -> CE; mk_counterexample(CE = [Cmds, _Seed]) when is_list(Cmds) -> CE; mk_counterexample(Cmds) -> S = state_after(?MODULE, Cmds), [Cmds, erlang:now(), [ {0, []} | [ {I, []} || I <- lists:seq(1, length(S#state.readers)) ] ] ++ [ {errors, []}, {events, []} ] ]. mk_counterexample(Cmds, Seed) -> [_Cmds, _Seed, Conj] = mk_counterexample(Cmds), [Cmds, Seed, Conj]. foo() -> erlang:now(). %% Helper functions fold(F, X) -> case catch(F(X)) of {'EXIT', _} -> fold1(F, X); Y -> [Y] end. fold1(F, [X|Xs]) -> fold(F, X) ++ fold(F, Xs); fold1(F, Tup) when is_tuple(Tup) -> fold1(F, tuple_to_list(Tup)); fold1(_F, _X) -> []. map(F, X) -> case catch(F(X)) of {'EXIT', _} -> map1(F, X); Y -> Y end. map1(F, [X|Xs]) -> [map(F, X)|map(F, Xs)]; map1(F, Tup) when is_tuple(Tup) -> list_to_tuple(map1(F, tuple_to_list(Tup))); map1(_F, X) -> X. zipwith(F, [X|Xs], [Y|Ys]) -> [F(X, Y)|zipwith(F, Xs, Ys)]; zipwith(_, _, _) -> []. uncommand(X) -> map(fun({var, N}) -> {not_var, N} end, map(fun({call, M, F, A}) -> {not_call, M, F, A} end, X)). recommand(X) -> map(fun({not_var, N}) -> {var, N} end, map(fun({not_call, M, F, A}) -> {call, M, F, A} end, X)). rename_from(X, Cmds) -> Bound = [ Y || {set, {var, Y}, _} <- Cmds ], Env = lists:zip(Bound, lists:seq(X, X + length(Bound) - 1)), map(fun ({var, Y}) -> case lists:keyfind(Y, 1, Env) of {Y, Z} -> {var, Z} end end, Cmds). really_delete_bitcask() -> [file:delete(X) || X <- filelib:wildcard(?BITCASK ++ "/*")], file:del_dir(?BITCASK), case file:read_file_info(?BITCASK) of {error, enoent} -> timer:sleep(10), ok; {ok, _} -> timer:sleep(10), really_delete_bitcask() end. mangle_temporal_relation_with_finite_time([{_Start, infinity, []}] = X) -> X; mangle_temporal_relation_with_finite_time([{Start, infinity, [_|_]=L}]) -> [{Start, Start+1, L}, {Start+1, infinity, []}]; mangle_temporal_relation_with_finite_time([H|T]) -> [H|mangle_temporal_relation_with_finite_time(T)]. %% This version of check_no_tombstones() plus the fold_keys %% implementation has a flaw that this QuickCheck model isn't smart %% enough to figure out: %% * if there's another fold/fold_keys that's happening in parallel %% to this fold, and %% * if key K already exists with some value V %% * if they keydir has been frozen, and %% * if key K has later been deleted, %% %% ... then this fold will return {K, V} in its results, and the %% fold's SeeTombstonesP will cause it to do a get(V) which %% will return not_found, then fold/fold_keys will invent a %% {tombstone, ...} tuple to be folded. %% %% Once upon a time, a version of bitcask_pulse.erl checked to see %% (after the test case had finished) if there were folds running in %% parallel: if so, then certain model failures were ignored because %% they were assumed to be caused by a frozen keydir plus multiple %% folds. I've elected not to put that kind of check back into this %% model. Instead, I believe that Bitcask's internal use of the %% keydir is now at a good point where check_no_tombstones() isn't %% necessary now. %% check_no_tombstones(Ref, Good) -> %% Res = bitcask:fold_keys(Ref, fun(K, Acc0) -> [K|Acc0] end, %% [], -1, -1, true), %% case [X || {tombstone, _} = X <- Res] of %% [] -> %% Good; %% Else -> %% {check_no_tombstones, Else} %% end. -endif. bitcask-2.1.0/eqc/bitcask_qc_fsm.erl0000644000232200023220000001560013655023466017740 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_qc_fsm). -export([create_stale_lock/0, corrupt_hint/2]). -include_lib("kernel/include/file.hrl"). -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_fsm.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("include/bitcask.hrl"). -define(TEST_DIR, filename:join(?TEST_FILEPATH, "bitcask.qc." ++ os:getpid())). -compile([export_all, nowarn_export_all]). -record(state,{ bitcask :: reference(), data = [] :: list(), keys :: list() }). %% Keys to use in the test -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). initial_state() -> init. initial_state_data() -> #state{}. init(_S) -> [{closed, {call, ?MODULE, set_keys, [list(key_gen())]}}]. closed(_S) -> [{opened, {call, bitcask, open, [?TEST_DIR, [read_write, {open_timeout, 0}, sync_strategy()]]}}, {closed, {call, bitcask_merge_delete, testonly__truncate_hint, [int(), int()]}}, {closed, {call, ?MODULE, corrupt_hint, [int(), int()]}}, {closed, {call, ?MODULE, create_stale_lock, []}}]. opened(S) -> [{closed, {call, bitcask, close, [S#state.bitcask]}}, {opened, {call, bitcask, get, [S#state.bitcask, key(S)]}}, {opened, {call, bitcask, put, [S#state.bitcask, key(S), value()]}}, {opened, {call, bitcask, delete, [S#state.bitcask, key(S)]}}, {opened, {call, bitcask, merge, [?TEST_DIR]}} ]. next_state_data(init, closed, S, _, {call, _, set_keys, [Keys]}) -> S#state{ keys = [<<"k">> | Keys] }; % ensure always one key next_state_data(closed, opened, S, Bcask, {call, bitcask, open, _}) -> S#state { bitcask = Bcask }; next_state_data(opened, closed, S, _, {call, _, close, _}) -> S#state { bitcask = undefined }; next_state_data(opened, opened, S, _, {call, bitcask, put, [_, Key, Value]}) -> S#state { data = orddict:store(Key, Value, S#state.data) }; next_state_data(opened, opened, S, _, {call, bitcask, delete, [_, Key]}) -> S#state { data = orddict:erase(Key, S#state.data) }; next_state_data(_From, _To, S, _Res, _Call) -> S. %% Precondition (for state data). %% Precondition is checked before command is added to the command sequence precondition(_From,_To,S,{call,_,get,[_,Key]}) -> lists:member(Key, S#state.keys); % check the key has not been shrunk away precondition(_From,_To,S,{call,_,put,[_,Key,_Val]}) -> lists:member(Key, S#state.keys); % check the key has not been shrunk away precondition(_From,_To,_S,{call,_,_,_}) -> true. postcondition(opened, opened, S, {call, _, get, [_, Key]}, not_found) -> case orddict:find(Key, S#state.data) of error -> true; {ok, Exp} -> {expected, Exp, got, not_found} end; postcondition(opened, opened, S, {call, _, get, [_, Key]}, {ok, Value}) -> case orddict:find(Key, S#state.data) of {ok, Value} -> true; Exp -> {expected, Exp, got, Value} end; postcondition(opened, opened, _S, {call, _, merge, [_TestDir]}, Res) -> case Res == ok of true -> true; false -> erlang:display({bad_merge_return, Res}), {expected, ok, got, Res} end; postcondition(_From,_To,_S,{call,_,_,_},_Res) -> true. prepare() -> error_logger:tty(false), application:load(bitcask), application:start(bitcask), application:set_env(bitcask, require_hint_crc, true). cleanup() -> application:stop(bitcask), application:unload(bitcask). prop_bitcask() -> ?SETUP(fun() -> prepare(), fun() -> cleanup() end end, ?FORALL(Cmds, commands(?MODULE), begin bitcask_merge_delete:testonly__delete_trigger(), [] = os:cmd("rm -rf " ++ ?TEST_DIR), {H,{_State, StateData}, Res} = run_commands(?MODULE,Cmds), case (StateData#state.bitcask) of undefined -> ok; Ref -> bitcask:close(Ref) end, application:unload(bitcask), aggregate(zip(state_names(H),command_names(Cmds)), equals(Res, ok)) end)). %% Weight for transition (this callback is optional). %% Specify how often each transition should be chosen weight(_From, _To,{call,_,close,_}) -> 10; weight(_From, _To,{call,_,truncate_hint,_}) -> 10; weight(_From, _To,{call,_,corrupt_hint,_}) -> 10; weight(_From,_To,{call,_,_,_}) -> 100. set_keys(_Keys) -> %% next_state sets the keys for use by key() ok. key_gen() -> ?SUCHTHAT(X, binary(), X /= <<>>). key(#state{keys = Keys}) -> elements(Keys). value() -> binary(). sync_strategy() -> {sync_strategy, oneof([none, o_sync])}. -endif. create_stale_lock() -> Fname = filename:join(?TEST_DIR, "bitcask.write.lock"), filelib:ensure_dir(Fname), ok = file:write_file(Fname, "102349430239 abcdef\n"). corrupt_hint(Seed, CorruptAt0) -> case filelib:wildcard(?TEST_DIR ++ "/*.hint") of [] -> ok; Hints-> Hint = lists:nth(1 + (abs(Seed) rem length(Hints)), Hints), {ok, Fi} = file:read_file_info(Hint), {ok, Fh} = file:open(Hint, [read, write, binary]), Size = Fi#file_info.size, CorruptAt = (1 + abs(CorruptAt0)) rem (Size+1), try {ok, Pos} = file:position(Fh, {eof, -CorruptAt}), {ok, <>} = file:pread(Fh, Pos, 1), BadByte = <<(bnot Byte)>>, io:format(user, "Corrupting from ~p to ~p at ~p size ~p\n", [Byte, BadByte, Pos, Size]), ok = file:pwrite(Fh, Pos, BadByte) catch _:Reason -> io:format(user, "corrupt failed corruptat=~p reason=~p\n", [CorruptAt, Reason]) after file:close(Fh) end end. bitcask-2.1.0/eqc/bitcask_qc.erl0000644000232200023220000003531013655023466017073 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_qc). -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("include/bitcask.hrl"). -compile([export_all, nowarn_export_all]). -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). -define(TEST_TIME, 30). % seconds -record(m_fstats, {key_bytes=0 :: integer(), live_keys=0 :: integer(), live_bytes=0 :: integer(), total_keys=0 :: integer(), total_bytes=0 :: integer()}). qc(P) -> qc(P, ?TEST_TIME). qc(P, TestTime) -> ?assert(eqc:quickcheck(?QC_OUT(eqc:testing_time(TestTime, P)))). keys() -> eqc_gen:non_empty(list(noshrink(eqc_gen:non_empty(binary())))). values() -> eqc_gen:non_empty(list(noshrink(binary()))). ops(Keys, Values) -> {oneof([put, delete, itr, itr_next, itr_release]), oneof(Keys), oneof(Values)}. apply_kv_ops([], Ref, KVs0, Fstats) -> bitcask_nifs:keydir_itr_release(get_keydir(Ref)), % release any iterators {KVs0, Fstats}; apply_kv_ops([{put, K, V} | Rest], Ref, KVs0, Fstats0) -> ok = bitcask:put(Ref, K, V), apply_kv_ops(Rest, Ref, orddict:store(K, V, KVs0), update_fstats(put, K ,orddict:find(K, KVs0), V, Fstats0)); apply_kv_ops([{delete, K, _} | Rest], Ref, KVs0, Fstats0) -> ok = bitcask:delete(Ref, K), case orddict:find(K, KVs0) of error -> apply_kv_ops(Rest, Ref, KVs0, Fstats0); {ok, deleted} -> apply_kv_ops(Rest, Ref, KVs0, Fstats0); OldVal -> apply_kv_ops(Rest, Ref, orddict:store(K, deleted, KVs0), update_fstats(delete, K, OldVal, ?TOMBSTONE0, Fstats0)) end; apply_kv_ops([{itr, _K, _} | Rest], Ref, KVs, Fstats) -> %% Don't care about result, just want to intermix with get/put bitcask_nifs:keydir_itr(get_keydir(Ref), -1, -1), apply_kv_ops(Rest, Ref, KVs, Fstats); apply_kv_ops([{itr_next, _K, _} | Rest], Ref, KVs, Fstats) -> %% Don't care about result, just want to intermix with get/put bitcask_nifs:keydir_itr_next(get_keydir(Ref)), apply_kv_ops(Rest, Ref, KVs, Fstats); apply_kv_ops([{itr_release, _K, _} | Rest], Ref, KVs, Fstats) -> %% Don't care about result, just want to intermix with get/put bitcask_nifs:keydir_itr_release(get_keydir(Ref)), apply_kv_ops(Rest, Ref, KVs, Fstats). %% Delete existing key (i.e. write tombstone) update_fstats(delete, K, {ok, OldV}, _NewV, Fstats0) -> #m_fstats{key_bytes = KB, live_keys = LK, live_bytes = LB} = Fstats0, TotalSz = total_sz(K, OldV), Fstats0#m_fstats{key_bytes = KB - size(K), live_keys = LK - 1, live_bytes = LB - TotalSz}; %% Update m_fstats record - this will be the aggregate of all files in the bitcask update_fstats(put, K, ErrDel, NewV, #m_fstats{key_bytes = KB, live_keys = LK, live_bytes = LB, total_keys = TK, total_bytes = TB} = Fstats) when ErrDel =:= error; ErrDel =:= {ok, deleted} -> %% Add for first time or update after deletion NewTotalSz = total_sz(K, NewV), Fstats#m_fstats{key_bytes = KB + size(K), live_keys = LK + 1, live_bytes = LB + NewTotalSz, total_keys = TK + 1, total_bytes = TB + NewTotalSz}; update_fstats(put, K, {ok, OldV}, NewV, #m_fstats{live_bytes = LB, total_keys = TK, total_bytes = TB} = Fstats) -> %% update existing key OldTotalSz = total_sz(K, OldV), NewTotalSz = total_sz(K, NewV), Fstats#m_fstats{live_bytes = LB + NewTotalSz - OldTotalSz, total_keys = TK + 1, total_bytes = TB + NewTotalSz}. check_fstats(Ref, Expect) -> Aggregate = fun({_FileId, FileLiveCount, FileTotalCount, FileLiveBytes, FileTotalBytes, _FileOldestTstamp, _FileNewestTstamp, _ExpEpoch}, {LiveCount0, TotalCount0, LiveBytes0, TotalBytes0}) -> {LiveCount0 + FileLiveCount, TotalCount0 + FileTotalCount, LiveBytes0 + FileLiveBytes, TotalBytes0 + FileTotalBytes} end, {KeyCount, KeyBytes, Fstats, _, _} = bitcask_nifs:keydir_info(get_keydir(Ref)), {LiveCount, TotalCount, LiveBytes, TotalBytes} = lists:foldl(Aggregate, {0, 0, 0, 0}, Fstats), ?assert(Expect#m_fstats.live_keys >= 0), ?assert(Expect#m_fstats.key_bytes >= 0), ?assert(Expect#m_fstats.live_keys >= 0), ?assert(Expect#m_fstats.live_bytes >= 0), ?assert(Expect#m_fstats.total_keys >= 0), ?assert(Expect#m_fstats.total_bytes >= 0), ?assertEqual(Expect#m_fstats.live_keys, KeyCount), ?assertEqual(Expect#m_fstats.key_bytes, KeyBytes), ?assertEqual(Expect#m_fstats.live_keys, LiveCount), ?assertEqual(Expect#m_fstats.live_bytes, LiveBytes), ?assertEqual(Expect#m_fstats.total_keys, TotalCount), ?assert(Expect#m_fstats.total_bytes =< TotalBytes). check_model(Ref, Model) -> F = fun({K, deleted}) -> ?assertEqual({K, not_found}, {K, bitcask:get(Ref, K)}); ({K, V}) -> ?assertEqual({K, {ok, V}}, {K, bitcask:get(Ref, K)}) end, lists:map(F, Model). total_sz(K, V) -> % Total size of bitcask entry in bytes ((32 + % crc 32 + % tstamps 16 + % key size 32) div 8) + % val size size(K) + size(V). prop_merge() -> ?LET({Keys, Values}, {keys(), values()}, ?FORALL({Ops, M1, M2}, {eqc_gen:non_empty(list(ops(Keys, Values))), choose(1,128), choose(1,128)}, begin Tm = tuple_to_list(os:timestamp()), Dir = lists:flatten( io_lib:format( filename:join(?TEST_FILEPATH, "bc.prop.merge.~w.~w.~w"), Tm)), ?cmd("rm -rf " ++ Dir), %% Open a bitcask, dump the ops into it and build %% a model of what SHOULD be in the data. Ref = bitcask:open(Dir, [read_write, {max_file_size, M1}]), try {Model, Fstats} = apply_kv_ops(Ops, Ref, [], #m_fstats{}), check_fstats(Ref, Fstats), check_model(Ref, Model), %% Apply the merge -- note that we keep the %% bitcask open so that a live keydir is %% available to the merge. Run in a seperate %% process so it gets cleaned up on crash %% so quickcheck can shrink correctly. Me = self(), proc_lib:spawn( fun() -> try Me ! bitcask:merge(Dir, [{max_file_size, M2}]) catch _:Err -> Me ! Err end end), receive X -> ?assertEqual(ok, X) end, %% Call needs_merge to close any "dead" files bitcask:needs_merge(Ref), %% Traverse the model and verify that retrieving %% each key returns the expected value. It's %% important to note that the model keeps %% tombstones on deleted values so we can attempt %% to retrieve those deleted values and check the %% corresponding tombstone path in bitcask. %% Verify that the bitcask contains exactly what %% we expect check_model(Ref, Model), true after bitcask:close(Ref) end, %% For each of the data files, validate that it has a valid hint file Validate = fun(Fname) -> {ok, S} = bitcask_fileops:open_file(Fname), try ?assertEqual({Fname, true}, {Fname, bitcask_fileops:has_valid_hintfile(S)}) after bitcask_fileops:close(S) end end, [Validate(Fname) || {_Ts, Fname} <- bitcask_fileops:data_file_tstamps(Dir)], ?cmd("rm -rf " ++ Dir), true end)). prop_fold() -> ?LET({Keys, Values, FoldOp}, {keys(), values(), oneof([fold, fold_keys])}, ?FORALL({Ops, M1}, {eqc_gen:non_empty(list(ops(Keys, Values))), choose(1,128)}, begin ?cmd("rm -rf " ++ filename:join(?TEST_FILEPATH, "bc.prop.fold")), %% Open a bitcask, dump the ops into it and build %% a model of what SHOULD be in the data. Ref = bitcask:open(filename:join(?TEST_FILEPATH, "bc.prop.fold"), [read_write, {max_file_size, M1}]), try {Model, Fstats} = apply_kv_ops(Ops, Ref, [], #m_fstats{}), check_fstats(Ref, Fstats), %% Build a list of the K/V pairs available to fold Actual = case FoldOp of fold_keys -> bitcask:fold_keys(Ref, fun(E, Acc0) -> K = E#bitcask_entry.key, {ok, V} = bitcask:get(Ref, K), [{K, V} | Acc0] end, []); fold -> bitcask:fold(Ref, fun(K, V, Acc0) -> [{K, V} | Acc0] end, []) end, %% Traverse the model and verify that retrieving %% each key returns the expected value. It's %% important to note that the model keeps %% tombstones on deleted values so we can attempt %% to retrieve those deleted values and check the %% corresponding tombstone path in bitcask. %% Verify that the bitcask contains exactly what %% we expect F = fun({K, deleted}) -> ?assert(false == lists:keymember(K, 1, Actual)); ({K, V}) -> ?assertEqual({K, V}, lists:keyfind(K, 1, Actual)) end, lists:map(F, Model) after bitcask:close(Ref) end, true end)). merge1_test_() -> {timeout, 60, fun merge1_test2/0}. merge1_test2() -> ?assert(eqc:check(prop_merge(), [{[{put,<<0>>,<<>>},{delete,<<0>>,<<>>}],1,1}])). merge2_test_() -> {timeout, 60, fun merge2_test2/0}. merge2_test2() -> ?assert(eqc:check(prop_merge(), [{[{put,<<1>>,<<>>},{delete,<<0>>,<<>>}],1,1}])). merge3_test_() -> {timeout, 60, fun merge3_test2/0}. merge3_test2() -> ?assert(eqc:check(prop_merge(), [{[{put,<<0>>,<<>>}, {delete,<<0>>,<<>>}, {delete,<<1>>,<<>>}], 1,1}])). merge4_test_() -> {timeout, 60, fun merge4_test2/0}. merge4_test2() -> ?assert(eqc:check(prop_merge(), [{[{itr,<<1>>,<<>>},{delete,<<0>>,<<>>}],1,1}])). merge5_test_() -> {timeout, 60, fun merge5_test2/0}. merge5_test2() -> ?assert(eqc:check(prop_merge(), [{[{put,<<"test">>,<<>>},{itr,<<"test">>,<<>>}, {delete,<<"test">>,<<>>},{delete,<<"test">>,<<>>}],1,1}])). merge6_test_() -> {timeout, 60, fun merge6_test2/0}. merge6_test2() -> ?assert(eqc:check(prop_merge(), [{[{itr,<<"test">>,<<>>},{put,<<"test">>,<<>>}, {delete,<<"test">>,<<>>},{delete,<<"test">>,<<>>}], 1,1}])). fold1_test_() -> {timeout, 60, fun fold1_test2/0}. fold1_test2() -> ?assert(eqc:check(prop_fold(), [{[{put,<<0>>,<<>>}, {itr,<<0>>,<<>>}, {delete,<<0>>,<<>>}, {itr_release,<<0>>,<<>>}, {put,<<0>>,<<>>}],1}])). fold2_test_() -> {timeout, 60, fun fold2_test2/0}. fold2_test2() -> ?assert(eqc:check(prop_fold(), [{[{put,<<1>>,<<>>}, {itr,<<0>>,<<0>>}, {put,<<1>>,<<0>>}, {itr_release,<<1>>,<<0>>}, {put,<<1>>,<<>>}],1}])). get_keydir(Ref) -> element(9, erlang:get(Ref)). -endif. bitcask-2.1.0/eqc/bitcask_qc_expiry.erl0000644000232200023220000002005213655023466020470 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_qc_expiry). -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("include/bitcask.hrl"). -include("stacktrace.hrl"). -compile([export_all, nowarn_export_all]). keys() -> eqc_gen:non_empty(list(eqc_gen:non_empty(binary()))). values() -> eqc_gen:non_empty(list(binary())). ops(Keys, Values) -> {oneof([put, delete]), oneof(Keys), oneof(Values)}. apply_kv_ops([], _Ref, KVs0) -> KVs0; apply_kv_ops([{put, K, V} | Rest], Ref, KVs0) -> ok = bitcask:put(Ref, K, V), apply_kv_ops(Rest, Ref, orddict:store({K, write_file(Ref)}, {V, current_tstamp()}, KVs0)); apply_kv_ops([{delete, K, _} | Rest], Ref, KVs0) -> ok = bitcask:delete(Ref, K), apply_kv_ops(Rest, Ref, orddict:store({K, write_file(Ref)}, {deleted, current_tstamp()}, KVs0)). write_file(Ref) -> %% Extract active write_file handle from Bitcask ref element(3, erlang:get(Ref)). current_tstamp() -> case erlang:get(meck_tstamp) of undefined -> next_tstamp(); % Set it up for us Value -> Value end. next_tstamp() -> Ts = case erlang:get(meck_tstamp) of undefined -> 1; Tstamp -> Tstamp + erlang:get(meck_tstamp_step) end, erlang:put(meck_tstamp, Ts), Ts. set_tstamp(Tstamp) -> erlang:put(meck_tstamp, Tstamp). set_tstamp_step(Step) -> erlang:put(meck_tstamp_step, Step). prop_expiry() -> ?SETUP(fun() -> meck:new(bitcask_time, [passthrough]), meck:expect(bitcask_time, tstamp, fun next_tstamp/0), fun() -> meck:unload() end end, ?LET({Keys, Values}, {keys(), values()}, ?FORALL({Ops, Expiry, ExpiryGrace, Timestep, M1}, {eqc_gen:non_empty(list(ops(Keys, Values))), choose(1,10), choose(1, 10), choose(5, 50), choose(5,128)}, ?IMPLIES(true, begin Dirname = filename:join(?TEST_FILEPATH, "bc.prop.expiry"), ?cmd("rm -rf " ++ Dirname), %% Initialize how many ticks each operation will %% increment the clock by set_tstamp(undefined), set_tstamp_step(Timestep), Bref = bitcask:open(Dirname, [read_write, % {log_needs_merge, true}, {frag_merge_trigger, disabled}, {dead_bytes_merge_trigger, disabled}, {small_file_threshold, disabled}, {frag_threshold, disabled}, {expiry_secs, Expiry}, {expiry_grace_secs, ExpiryGrace}, {max_file_size, M1}]), try %% Dump the ops into the bitcask and build a model of %% what SHOULD be in the data. Model = apply_kv_ops(Ops, Bref, []), %% Assist our model's calculations by incrementing %% the clock one more time. _ = next_tstamp(), %% Close the writing file to ensure that it's included %% in the needs_merge calculation bitcask:close_write_file(Bref), %% Identify items in the Model that should be expired ExpireCutoff = erlang:max(current_tstamp() + Timestep - erlang:max(Expiry - ExpiryGrace, 0), -1), {Expired, _Live} = lists:partition(fun({_K, {_Value, Tstamp}}) -> Tstamp < ExpireCutoff end, Model), % io:format(user, "Cutoff: ~p\nExpired: ~120p\nLive: ~120p\n", % [ExpireCutoff, Expired, Live]), AllDeleteOps = lists:all(fun({delete,_,_}) -> true; (_) -> false end, Ops), %% Check that needs_merge has expected result case {AllDeleteOps, Expired} of {true, _} -> ?assertEqual(false, bitcask:needs_merge(Bref)), true; {false, []} -> ?assertEqual(false, bitcask:needs_merge(Bref)), true; {false, _} -> ?assertMatch({true, _}, bitcask:needs_merge(Bref)), true end catch ?_exception_(X, Y, StackToken) -> io:format(user, "exception: ~p ~p @ ~p\n", [X,Y, ?_get_stacktrace_(StackToken)]), test_exception after bitcask:close(Bref) end end)))). validate_expired([], _) -> ok; validate_expired([{_K, {deleted, _}} | Rest], Actual) -> validate_expired(Rest, Actual); validate_expired([{K, {Value, _Tstamp}} | Rest], Actual) -> % ?debugFmt("~p (~p) should be expired; actual: ~p\n", [K, Tstamp, Actual]), ?assert(not lists:member({K, Value}, Actual)), validate_expired(Rest, Actual). validate_live([], _) -> ok; validate_live([{K, {deleted, _}} | Rest], Actual) -> % ?debugFmt("~p should be deleted; actual: ~p\n", [K, Actual]), ?assert(not lists:keymember(K, 1, Actual)), validate_live(Rest, Actual); validate_live([{K, {Value, _}} | Rest], Actual) -> % ?debugFmt("~p should have value ~p; actual: ~p\n", [K, Value, Actual]), ?assert(lists:member({K, Value}, Actual)), validate_live(Rest, Actual). -endif. bitcask-2.1.0/eqc/generic_qc_fsm.erl0000644000232200023220000003603013655023466017734 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% Testing testing testing %% %% Copyright (c) 2014 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(generic_qc_fsm). %% Borrowed heavily from bitcask_qc_fsm.erl %% Example usage: %% %% make clean %% make %% rebar skip_deps=true eunit suites=XX %% cp ebin/*app* .eunit %% erlc -I deps/faulterl/include -o deps/faulterl/ebin priv/scenario/*erl && deps/faulterl/ebin/make_intercept_c.escript trigger_commonpaths yo && env `deps/faulterl/ebin/example_environment.sh $PWD/yo` erl -sname foo -pz .eunit deps/*/ebin %% You should now have an Erlang shell. %% eqc:quickcheck(eqc:testing_time(1, generic_qc_fsm:prop(false, false))). %% This will run without fault injection for 1 second, to allow the VM to %% auto-load the BEAM & shared lib files that we need. %% Additional output on the console: %% %% "{" is an open %% "}" is a close %% "" is a fold operation, start & finish %% "" is an add filler operation, start & finish %% eqc:quickcheck(eqc:testing_time(15*60, generic_qc_fsm:prop(true, false))). %% Run with fault injection on for 15 minutes. %% %% When FI is enabled, there is a lot of additional output on the console. %% %% "," is a failed open or close %% "pm" is a put operation that maybe-succeeded %% "dm" is a delete operation that maybe-succeeded -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -include_lib("eqc/include/eqc_fsm.hrl"). -include_lib("kernel/include/file.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("include/bitcask.hrl"). -compile([export_all, nowarn_export_all]). -record(state,{ handle :: term(), dir :: term(), data = [] :: term(), keys :: term() }). %% Keys to use in the test %% Used for output within EUnit... -define(QC_FMT(Fmt, Args), io:format(user, Fmt, Args)). -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). -define(TEST_DIR, filename:join(?TEST_FILEPATH, "generic.qc")). initial_state() -> init. initial_state_data() -> #state{}. init(_S) -> [{closed, {call, ?MODULE, set_keys, [non_empty(list(key_gen(0))), {var,parameter_test_dir}]}}]. closed(#state{dir=TestDir}) -> [{opened, {call, ?MODULE, open, [TestDir,[read_write, {open_timeout, 60}, {expiry_secs, 0}, {max_file_size, 1024*1024} ]]}} ]. opened(S) -> [{closed, {call, ?MODULE, close, [S#state.handle]}}, {opened, {call, ?MODULE, get, [S#state.handle, key(S)]}}, {opened, {call, ?MODULE, put, [S#state.handle, key(S), value()]}}, {opened, {call, ?MODULE, put_filler, [S#state.handle, gen_filler_keys(), gen_filler_size()]}}, {opened, {call, ?MODULE, delete, [S#state.handle, key(S)]}}, {opened, {call, ?MODULE, fold_all, [S#state.handle]}}, {opened, {call, ?MODULE, merge, [S#state.dir]}} ]. next_state_data(init, closed, S, _, {call, _, set_keys, [Keys, TestDir]}) -> S#state{ keys = Keys, dir = TestDir }; next_state_data(closed, opened, S, Handle, {call, _, open, _}) -> S#state { handle = Handle }; next_state_data(opened, closed, S, _, {call, _, close, _}) -> S#state { handle = undefined }; next_state_data(opened, opened, S, _, {call, _, put, [_, Key, Value]}) -> S#state { data = orddict:store(Key, Value, S#state.data) }; next_state_data(opened, opened, S, _, {call, _, delete, [_, Key]}) -> S#state { data = orddict:erase(Key, S#state.data) }; next_state_data(_From, _To, S, _Res, _Call) -> S. precondition(_From,_To,S,{call,_,put,[_H, K, _V]}) -> lists:member(K, S#state.keys); precondition(_From,_To,S,{call,_,get,[_H, K]}) -> lists:member(K, S#state.keys); precondition(_From,_To,S,{call,_,delete,[_H, K]}) -> lists:member(K, S#state.keys); precondition(_From,_To,_S,_Call) -> true. postcondition(_OldSt, _NewSt, _S, {call, _, _Func, _Args}, _Res) -> true. prepare() -> ok. cleanup(_) -> ok. prop_correct() -> prop(false). prop(FI_enabledP) -> prop(FI_enabledP, false). prop(FI_enabledP, VerboseP) -> _ = faulterl_nif:poke("bc_fi_enabled", 0, <<0:8/native>>, false), ?FORALL({Cmds, Seed}, {commands(?MODULE), choose(1,99999)}, begin faulterl_nif:poke("bc_fi_enabled", 0, <<0:8/native>>, false), [catch erlang:garbage_collect(Pid) || Pid <- erlang:processes()], {Ta, Tb, Tc} = os:timestamp(), TestDir = ?TEST_DIR ++ lists:flatten(io_lib:format(".~w.~w.~w", [Ta, Tb, Tc])), ok = file:make_dir(TestDir), Env = [{parameter_test_dir, TestDir}], event_logger:start_link(), if FI_enabledP -> ok = faulterl_nif:poke("bc_fi_enabled", 0, <<1:8/native>>, false), VerboseI = if VerboseP -> 1; true -> 0 end, ok = faulterl_nif:poke("bc_fi_verbose", 0, <>, false), ok = faulterl_nif:poke("bc_fi_random_seed", 0, <>, false), %% io:format("Seed=~p,", [Seed]), ok = faulterl_nif:poke("bc_fi_random_reseed", 0, <<1:8/native>>, false); true -> ok end, event_logger:start_logging(), {H,{_State, StateData}, Res} = run_commands(?MODULE,Cmds,Env), _ = faulterl_nif:poke("bc_fi_enabled", 0, <<0:8/native>>, false), case (StateData#state.handle) of Handle when is_binary(Handle) orelse is_reference(Handle) -> catch (close(Handle)); _ -> true end, %% application:unload(bitcask), Trace0 = event_logger:get_events(), Trace = remove_timestamps(Trace0), Sane = verify_trace(Trace), ok = really_delete_dir(TestDir), ?WHENFAIL( begin SimpleTrace = simplify_trace(Trace), ?QC_FMT("Trace: ~P\nverify_trace: ~P\n", [SimpleTrace, 25, Sane, 25]) end, aggregate(zip(state_names(H),command_names(Cmds)), conjunction([{postconditions, equals(Res, ok)}, {verify_trace, Sane}]))) end). remove_timestamps(Trace) -> [Event || {_TS, Event} <- Trace]. verify_trace([]) -> true; verify_trace([{set_keys, Keys}|TraceTail]) -> Dict0 = dict:from_list([{K, [not_found]} || K <- Keys]), {Bool, _D} = lists:foldl( fun({get, How, K, V}, {true, D}) -> PrefixLen = byte_size(K) - 4, <<_:PrefixLen/binary, Suffix:32>> = K, if Suffix == 0 -> Vs = dict:fetch(K, D), case lists:member(V, Vs) of true -> {true, D}; false -> {{get,How,K,expected,Vs,got,V}, D} end; true -> %% Filler, skip {true, D} end; ({put, yes, K, V}, {true, D}) -> {true, dict:store(K, [V], D)}; ({put, maybe, K, V, _Err}, {true, D}) -> io:format(user, "pm", []), case dict:find (K, D) of {ok, Vs} -> {true, dict:store(K, [V|Vs], D)}; error -> {true, dict:store(K, [not_found, V], D)} end; ({delete, yes, K}, {true, D}) -> {true, dict:store(K, [not_found], D)}; ({delete, maybe, K, _Err}, {true, D}) -> io:format(user, "dm", []), Vs = dict:fetch(K, D), {true, dict:store(K, [not_found|Vs], D)}; ({fold, start, _ID}, Acc) -> Acc; ({fold, failed, _ID}, Acc) -> Acc; ({fold, done, ID}, {true, D}) -> Trc1 = lists:dropwhile( fun({fold, start, I}) when I == ID -> false; (_) -> true end, TraceTail), Trc2 = lists:takewhile( fun({fold, done, I}) when I == ID -> false; (_) -> true end, Trc1), FoldGotDict = dict:from_list( [{K, V} || {get, fold, K, V} <- Trc2]), Good = dict:fold( fun(K, MaybeVs, true) -> case dict:find(K, FoldGotDict) of error -> case lists:member(not_found, MaybeVs) of true -> true; false -> {fold, did_not_find, K, MaybeVs} end; {ok, Val} -> case lists:member(Val, MaybeVs) of true -> true; false -> {fold, wrong_val, K, expected, MaybeVs, got, Val} end end; (_, _, Acc) -> Acc end, true, D), {Good, D}; (open, Acc) -> Acc; ({open, _}, Acc) -> Acc; (close, Acc) -> Acc; (_Else, Acc) -> io:format(user, "verify_trace: ~P\n", [_Else, 20]), Acc end, {true, Dict0}, TraceTail), Bool. simplify_trace(Trace) -> lists:filter( fun({put, _, K, _}) -> zero_suffix_p(K); ({delete, _, K}) -> zero_suffix_p(K); ({delete, _, K, _}) -> zero_suffix_p(K); ({get, _, K, _}) -> zero_suffix_p(K); ({open, _}) -> true; ({close, _}) -> true; (_) -> false end, Trace). zero_suffix_p(K) -> PrefixLen = byte_size(K) - 4, <<_:PrefixLen/binary, Suffix/binary>> = K, Suffix == <<0,0,0,0>>. %% Weight for transition (this callback is optional). %% Specify how often each transition should be chosen weight(_From, _To,{call,_,close,_}) -> 25; weight(_From, _To,{call,_,merge,_}) -> 5; weight(_From,_To,{call,_,_,_}) -> 100. set_keys(Keys, _TestDir) -> %% next_state sets the keys for use by key() event_logger:event({set_keys, Keys}), ok. key_gen(SuffixI) -> noshrink(?LET(Prefix, ?SUCHTHAT(X, binary(), X /= <<>>), <>)). key(#state{keys = Keys}) -> elements(Keys). value() -> noshrink(binary()). sync_strategy() -> {sync_strategy, oneof([none])}. gen_filler_keys() -> noshrink({choose(1, 50), non_empty(binary())}). gen_filler_size() -> noshrink(choose(1, 128*1024)). really_delete_dir(Dir) -> [file:delete(X) || X <- filelib:wildcard(Dir ++ "/*")], [file:delete(X) || X <- filelib:wildcard(Dir ++ "/*/*")], [file:del_dir(X) || X <- filelib:wildcard(Dir ++ "/*")], case file:del_dir(Dir) of ok -> ok; {error,enoent} -> ok; Else -> Else end. open(Dir, Opts) -> case bitcask:open(Dir, Opts) of H when is_reference(H) -> io:format(user, "{", []), H; Else -> io:format(user, ",", []), Else end. close(not_open) -> io:format(user, ",", []), ignored; close(H) -> io:format(user, "}", []), bitcask:close(H). get(not_open, _K) -> ignored; get(H, K) -> %% io:format(user, "get ~p,", [K]), case bitcask:get(H, K) of {ok, V} = X -> event_logger:event({get, get, K, V}), X; not_found = X -> event_logger:event({get, get, K, not_found}), X; Else -> Else end. put(not_open, _Ks, _V) -> ignored; put(H, K, V) -> %% io:format(user, "put ~p,", [K]), case bitcask:put(H, K, V) of ok = X -> event_logger:event({put, yes, K, V}), X; X -> event_logger:event({put, maybe, K, V, X}), X end. put_filler(not_open, _Ks, _V) -> ignored; put_filler(H, {NumKs, Prefix}, ValSize) -> io:format(user, ">, [put(H, <>, Val) || N <- lists:seq(1, NumKs)], io:format(user, ">", []), ok. delete(not_open, _K) -> ignored; delete(H, K) -> %% io:format(user, "delete ~p,", [K]), case bitcask:delete(H, K) of ok = X -> event_logger:event({delete, yes, K}), X; X -> event_logger:event({delete, maybe, K, X}), X end. fold_all(not_open) -> ignored; fold_all(H) -> F = fun(K, V, Acc) -> event_logger:event({get, fold, K, V}), [{K,V}|Acc] end, io:format(user, " event_logger:event({fold, failed, ID}); _Yay -> event_logger:event({fold, done, ID}) end, io:format(user, ">", []), ok. merge(not_open) -> ignored; merge(H) -> bitcask:merge(H). -endif. bitcask-2.1.0/include/0000755000232200023220000000000013655023466015135 5ustar debalancedebalancebitcask-2.1.0/include/bitcask.hrl0000644000232200023220000000546313655023466017274 0ustar debalancedebalance -record(bitcask_entry, { key :: binary(), file_id :: integer(), total_sz :: integer(), offset :: integer() | binary(), tstamp :: integer() }). %% @type filestate(). -record(filestate, {mode :: 'read_only' | 'read_write', % File mode: read_only, read_write filename :: string(), % Filename tstamp :: integer(), % Tstamp portion of filename fd :: port(), % File handle hintfd :: port() | undefined, % File handle for hints hintcrc=0 :: integer(), % CRC-32 of current hint ofs :: non_neg_integer(), % Current offset for writing l_ofs=0 :: non_neg_integer(), % Last offset written to data file l_hbytes=0 :: non_neg_integer(),% Last # bytes written to hint file l_hintcrc=0 :: non_neg_integer()}). % CRC-32 of current hint prior to last write -record(file_status, { filename :: string(), fragmented :: integer(), dead_bytes :: integer(), total_bytes :: integer(), oldest_tstamp :: integer(), newest_tstamp :: integer(), expiration_epoch :: non_neg_integer() }). -define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). -define(TOMBSTONE_PREFIX, "bitcask_tombstone"). -define(TOMBSTONE0_STR, ?TOMBSTONE_PREFIX). -define(TOMBSTONE0, <>). -define(TOMBSTONE1_STR, ?TOMBSTONE_PREFIX "1"). -define(TOMBSTONE1_BIN, <>). -define(TOMBSTONE2_STR, ?TOMBSTONE_PREFIX "2"). -define(TOMBSTONE2_BIN, <>). -define(TOMBSTONE0_SIZE, size(?TOMBSTONE0)). % Size of tombstone + 32 bit file id -define(TOMBSTONE1_SIZE, (size(?TOMBSTONE1_BIN)+4)). -define(TOMBSTONE2_SIZE, (size(?TOMBSTONE2_BIN)+4)). % Change this to the largest size a tombstone value can have if more added. -define(MAX_TOMBSTONE_SIZE, ?TOMBSTONE2_SIZE). % Notice that tombstone version 1 and 2 are the same size, so not tested below -define(IS_TOMBSTONE_SIZE(S), (S == ?TOMBSTONE0_SIZE orelse S == ?TOMBSTONE1_SIZE)). -define(OFFSETFIELD_V1, 64). -define(TOMBSTONEFIELD_V2, 1). -define(OFFSETFIELD_V2, 63). -define(TSTAMPFIELD, 32). -define(KEYSIZEFIELD, 16). -define(TOTALSIZEFIELD, 32). -define(VALSIZEFIELD, 32). -define(CRCSIZEFIELD, 32). -define(HEADER_SIZE, 14). % 4 + 4 + 2 + 4 bytes -define(MAXKEYSIZE, 2#1111111111111111). -define(MAXVALSIZE, 2#11111111111111111111111111111111). -define(MAXOFFSET_V2, 16#7fffffffffffffff). % max 63-bit unsigned %% for hintfile validation -define(CHUNK_SIZE, 65535). -define(MIN_CHUNK_SIZE, 1024). -define(MAX_CHUNK_SIZE, 134217728). -define(TEST_FILEPATH, "_build/test/log"). bitcask-2.1.0/Makefile0000644000232200023220000000077613655023466015164 0ustar debalancedebalance.PHONY: compile rel cover test dialyzer eqc REBAR=./rebar3 compile: $(REBAR) compile clean: $(REBAR) clean cover: test $(REBAR) cover test: compile $(REBAR) eunit dialyzer: $(REBAR) dialyzer eqc: $(REBAR) eqc xref: $(REBAR) xref PULSE_TESTING_TIME ?= 30 pulse: compile mkdir -p .pulse cp eqc/pulse/Emakefile .pulse cp _build/default/lib/bitcask/ebin/bitcask.app .pulse (cd .pulse; \ erl -make; \ erl -noshell -s bitcask_pulse run_tests $(PULSE_TESTING_TIME)) check: test dialyzer xref bitcask-2.1.0/package/0000755000232200023220000000000013655023466015105 5ustar debalancedebalancebitcask-2.1.0/package/rpm/0000755000232200023220000000000013655023466015703 5ustar debalancedebalancebitcask-2.1.0/package/rpm/SPECS/0000755000232200023220000000000013655023466016560 5ustar debalancedebalancebitcask-2.1.0/package/rpm/SPECS/bitcask.spec0000644000232200023220000000273413655023466021062 0ustar debalancedebalance# BuildArch should be determined automatically. Use of setarch on x86-64 # platform will allow one to build ix86 only # # _revision, _release, and _version should be defined on the rpmbuild command # line like so: # # --define "_version 0.9.1.19.abcdef" --define "_release 7" \ # --define "_revision 0.9.1-19-abcdef" Name: bitcask Version: %{_version} Release: %{_release}%{?dist} License: GPLv2 Group: Development/Libraries Source: http://downloads.basho.com/%{name}/%{name}-%{_revision}/%{name}-%{_revision}.tar.gz URL: http://basho.com/ Vendor: Basho Technologies Packager: Basho Support BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root Summary: Because you need another a key/value storage engine %description Because you need another a key/value storage engine %define __prelink_undo_cmd /bin/cat prelink library %prep %setup -n %{name}-%{_revision} %build mkdir %{name} ERL_FLAGS="-smp enable" make %install mkdir -p %{buildroot}%{_libdir}%{name} #Copy all necessary lib files etc. cp -r $RPM_BUILD_DIR/%{name}-%{_revision}/ebin %{buildroot}%{_libdir}%{name} cp -r $RPM_BUILD_DIR/%{name}-%{_revision}/priv %{buildroot}%{_libdir}%{name} # I don't see how the source is useful at the moment #cp -r $RPM_BUILD_DIR/%{name}-%{_revision}/src %{buildroot}%{_libdir}%{name} %files %defattr(-,root,root) %dir %{_libdir}%{name} %{_libdir}%{name}/* %clean rm -rf %{buildroot} %changelog * Wed May 26 2010 Ryan Tilder 0.1-1 - Initial packaging bitcask-2.1.0/package/rpm/Makefile0000644000232200023220000000201213655023466017336 0ustar debalancedebalance build: $(PKGERDIR)/SOURCES/$(APP)-$(REVISION).tar.gz rpmbuild @echo "BITCASK_TAG = $(BITCASK_TAG)" @echo "REVISION = $(REVISION)" @echo "RELEASE = $(RELEASE)" rpmbuild --define '_topdir $(CURDIR)/rpmbuild' \ --define '_sourcedir $(CURDIR)/$(PKGERDIR)/SOURCES' \ --define '_specdir $(CURDIR)/$(PKGERDIR)/SPECS' \ --define '_rpmdir $(CURDIR)/packages' \ --define '_srcrpmdir $(CURDIR)/packages' \ --define "_revision $(REVISION)" \ --define "_version $(PKG_VERSION)" \ --define "_release $(RELEASE)" \ -ba $(PKGERDIR)/SPECS/$(APP).spec mv packages/*/$(APP)-$(PKG_VERSION)-$(RELEASE)*.rpm packages rm -rf packages/i?86 packages/x86_64 rpmbuild: @mkdir -p rpmbuild/BUILD @mkdir -p packages # In case it doesn't exist because there aren't any patches to apply $(PKGERDIR)/SOURCES: @mkdir -m 0755 -p $(PKGERDIR)/SOURCES $(PKGERDIR)/SOURCES/$(APP)-$(REVISION).tar.gz: $(APP)-$(REVISION).tar.gz \ $(PKGERDIR)/SOURCES cp $(APP)-$(REVISION).tar.gz $(PKGERDIR)/SOURCES $(PKGERDIR)/pkgclean: @echo bitcask-2.1.0/package/Makefile0000644000232200023220000000203013655023466016540 0ustar debalancedebalanceOS = $(shell uname -s) KERNEL = $(shell uname -r) ifeq ($(OS),Linux) PKGER = $(shell cat /etc/redhat-release 2> /dev/null) ifeq ($(PKGER),) PKGER = debuild PKGERDIR = deb else PKGER = rpmbuild PKGERDIR = rpm endif endif ifeq ($(OS),SunOS) PKGER = make PKGERDIR = solaris DISTRO = $(shell awk '{ if (NR==1) print $$1; };' /etc/release) endif APP = $(shell echo "$(REPO)" | sed -e 's/_/-/g') # Assumes CURDIR is $(REPO)/package/ RIAK_PATH ?= .. RELEASE ?= $(APP)-$(REVISION).tar.gz: ../$(BITCASK_TAG).tar.gz ln -s $< $@ pkgclean: $(PKGERDIR)/pkgclean rm -rf $(APP)-$(REVISION).tar.gz working rpmbuild debuild packages pkgcheck: $(if $(BITCASK_TAG),,$(error "You can't generate a release tarball from a non-tagged revision. Run 'git checkout ', then 'make dist'")) $(if $(RELEASE),,$(error "You must provide a package release number via RELEASE= on the command line")) @echo "Packaging \"$(BITCASK_TAG)\"" # The heavy lifting is done by the individual packager Makefiles package: pkgcheck build include $(PKGERDIR)/Makefile bitcask-2.1.0/package/deb/0000755000232200023220000000000013655023466015637 5ustar debalancedebalancebitcask-2.1.0/package/deb/files0000644000232200023220000000003713655023466016664 0ustar debalancedebalancebitcask_0.1_i386.deb net extra bitcask-2.1.0/package/deb/control0000644000232200023220000000061613655023466017245 0ustar debalancedebalanceSource: bitcask Section: net Priority: extra Maintainer: Basho Support Build-Depends: debhelper (>= 7) Standards-Version: 3.8.3 Homepage: http://basho.com/ Package: bitcask Architecture: any Depends: adduser, logrotate, ${shlibs:Depends}, ${misc:Depends} Description: Because you need another a key/value storage engine Because you need another a key/value storage engine bitcask-2.1.0/package/deb/compat0000644000232200023220000000000213655023466017035 0ustar debalancedebalance7 bitcask-2.1.0/package/deb/Makefile0000644000232200023220000000137713655023466017307 0ustar debalancedebalanceBUILDPATH = debuild/$(APP)-$(REVISION) build: $(BUILDPATH)/debian \ debuild/$(APP)_$(REVISION).orig.tar.gz export DEBFULLNAME="Basho Buildbot Packager"; \ export DEBEMAIL="support@basho.com"; \ dch --noquery -c $(BUILDPATH)/debian/changelog \ -b -v "$(REVISION)-$(RELEASE)" "pants on head" cd $(BUILDPATH) && debuild --no-lintian \ -e REVISION=$(REVISION) \ -e RELEASE=$(RELEASE) \ -uc -us mkdir -p packages mv debuild/$(APP)_$(REVISION)-$(RELEASE)_*.deb packages $(BUILDPATH): $(APP)-$(REVISION).tar.gz mkdir -p debuild tar xz -C debuild -f $^ $(BUILDPATH)/debian: $(BUILDPATH) cp -a $(PKGERDIR) $@ rm -rf $@/.hg $@/Makefile $@/.*.swp debuild/$(APP)_$(REVISION).orig.tar.gz: $(APP)-$(REVISION).tar.gz cp $^ $@ $(PKGERDIR)/pkgclean: @echo bitcask-2.1.0/package/deb/postrm0000644000232200023220000000203213655023466017103 0ustar debalancedebalance#!/bin/sh # postrm script for riak # # see: dh_installdeb(1) # summary of how this script can be called: # * `remove' # * `purge' # * `upgrade' # * `failed-upgrade' # * `abort-install' # * `abort-install' # * `abort-upgrade' # * `disappear' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package case "$1" in purge) if [ -d /var/lib/riak/lib/bitcask-0.1 ]; then rm -r /var/lib/riak/lib/bitcask-0.1 fi ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 ;; esac # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 bitcask-2.1.0/package/deb/dirs0000644000232200023220000000003513655023466016521 0ustar debalancedebalanceusr/lib/riak/lib/bitcask-0.1 bitcask-2.1.0/package/deb/rules0000644000232200023220000000213313655023466016713 0ustar debalancedebalance#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 package=bitcask CFLAGS= LDFLAGS= build: ERL_FLAGS="-smp enable" make touch build clean: dh_clean rm -f build # Add here commands to clean up after the build process. make clean install: build dh_testdir dh_testroot dh_installdirs cp -R src debian/$(package)/usr/lib/riak/lib/$(package)-$(REVISION) cp -R ebin debian/$(package)/usr/lib/riak/lib/$(package)-$(REVISION) cp -R priv debian/$(package)/usr/lib/riak/lib/$(package)-$(REVISION) binary-indep: install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: install dh_strip -a dh_compress -a dh_installdeb dh_gencontrol dh_builddeb binary: binary-indep binary-arch bitcask-2.1.0/package/deb/changelog0000644000232200023220000000022613655023466017511 0ustar debalancedebalancebitcask (8) unstable; urgency=low * Initial release of bitcask key/value store -- Ryan Tilder Wed, 26 May 2010 16:20:40 UTC bitcask-2.1.0/package/deb/bitcask.substvars0000644000232200023220000000001613655023466021232 0ustar debalancedebalancemisc:Depends= bitcask-2.1.0/package/deb/copyright0000644000232200023220000000204013655023466017566 0ustar debalancedebalanceThis package was debianized by Basho Support on Wed, 26 May 2010 16:20:40 UTC It was downloaded from http://downloads.basho.com/bitcask/bitcask-0.1/ Upstream Author(s): Copyright: 2007-2010 Basho Technologies License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The Debian packaging is: 2007-2010 Basho Technologies and is licensed under the Apache License, Version 2.0 # Please also look if there are files or directories which have a # different copyright/license attached and list them here. bitcask-2.1.0/package/deb/bitcask.debhelper.log0000644000232200023220000000011413655023466021707 0ustar debalancedebalancedh_installdirs dh_strip dh_compress dh_installdeb dh_gencontrol dh_builddeb bitcask-2.1.0/package/solaris/0000755000232200023220000000000013655023466016561 5ustar debalancedebalancebitcask-2.1.0/package/solaris/pkginfo.tmpl0000644000232200023220000000054613655023466021121 0ustar debalancedebalance# Set this for the default basedir for relocatable packages BASEDIR=/opt/riak CLASSES=none TZ=EST PATH=/sbin:/usr/sbin:/usr/bin:/usr/sadm/install/bin PKG=@@PKG@@ NAME=@@PKGNAME@@ VERSION=@@VERSION@@ CATEGORY=application DESC=Because you need another a key/value storage engine VENDOR=Basho Technologies EMAIL=riak@basho.com PKGSAV=/var/sadm/pkg/@@PKG@@/save bitcask-2.1.0/package/solaris/Makefile0000644000232200023220000000305713655023466020226 0ustar debalancedebalance# requires GNU make PKG = BASHO$(APP) # possible ARCH values are i386, sparc, all ARCH = $(shell uname -p) SOLARIS_VER ?= $(shell echo "$(KERNEL)" | sed -e 's/^5\.//') PKGFILE = $(PKG)-$(REVISION)-$(RELEASE)-$(DISTRO)$(SOLARIS_VER)-$(ARCH).pkg BITCASK_LIB ?= lib/$(APP)-$(REVISION) build: buildrel pkginfo prototype cp $(PKGERDIR)/copyright $(PKGERDIR)/depend . mkdir -p pkgbuild packages pkgmk -o -d pkgbuild -a $(ARCH) touch packages/$(PKGFILE) pkgtrans -s pkgbuild packages/$(PKGFILE) $(PKG) rm -r pkgbuild/$(PKG) gzip packages/$(PKGFILE) @echo @echo Wrote: $(CURDIR)/packages/$(PKGFILE).gz @echo # Build the release we need to package buildrel: @# Ye Olde Bourne Shell on Solaris means we have to do it old school PATH=/opt/erlang/R13B04-$(DISTRO)$(SOLARIS_VER):$${PATH}; \ export PATH; \ echo "Using `which erl` to build"; \ $(MAKE) -C $(BITCASK_PATH) pkginfo: sed -e 's/@@VERSION@@/$(REVISION)-$(RELEASE)/g' \ -e 's/@@PKG@@/$(PKG)/g' \ -e 's/@@PKGNAME@@/$(APP)/g' \ < $(PKGERDIR)/pkginfo.tmpl > pkginfo # NOTE! The instances of riak below shouldn't change prototype: echo "i pkginfo" > prototype echo "i copyright" >> prototype echo "i depend" >> prototype echo '' >> prototype echo "d none lib/$(APP)-$(REVISION) 0755 riak riak" >> prototype pkgproto $(BITCASK_PATH)/ebin=$(BITCASK_LIB)/ebin >> prototype pkgproto $(BITCASK_PATH)/priv=$(BITCASK_LIB)/priv >> prototype sed -i -e 's/basho other/riak riak/' \ -e 's/buildbot other/riak riak/' prototype $(PKGERDIR)/pkgclean: rm -rf copyright depend pkgbuild pkginfo prototype bitcask-2.1.0/package/solaris/depend0000644000232200023220000000032613655023466017744 0ustar debalancedebalance# Same dependencies as Erlang P SUNWlibmsr Math & Microtasking Libraries (Root) P SUNWlibms Math & Microtasking Libraries (Usr) P SUNWopensslr OpenSSL (Root) P SUNWopenssl-libraries OpenSSL Libraries (Usr) bitcask-2.1.0/package/solaris/copyright0000644000232200023220000002367713655023466020533 0ustar debalancedebalance Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS bitcask-2.1.0/rebar.config0000644000232200023220000000355513655023466016004 0ustar debalancedebalance{erl_opts, [debug_info, warn_untyped_record, {platform_define, "^[0-9]+", namespaced_types}, {platform_define, "^[2-9][1-9]\..*", dirty_file_nif}]}. {port_specs, [{"priv/bitcask.so", ["c_src/*.c"]}]}. {port_env, [ {"DRV_CFLAGS", "-g -Wall -fPIC $ERL_CFLAGS"}, %% Solaris specific flags {"solaris.*-64$", "CFLAGS", "-D_REENTRANT -m64"}, {"solaris.*-64$", "LDFLAGS", "-m64"}, %% OS X Leopard flags for 64-bit {"darwin9.*-64$", "CFLAGS", "-m64"}, {"darwin9.*-64$", "LDFLAGS", "-arch x86_64"}, %% OS X Snow Leopard flags for 32-bit {"darwin10.*-32$", "CFLAGS", "-m32"}, {"darwin10.*-32$", "LDFLAGS", "-arch i386"} ]}. {profiles, [ {prod, [ {erl_opts, [warnings_as_errors]} ]}, {test, [ {deps, [meck, {cuttlefish, {git, "https://github.com/basho/cuttlefish.git", {branch, "develop-3.0"}}} ]}, {eunit_opts, [verbose]} ]}, {eqc, [ {deps, [meck, {faulterl, ".*", {git, "https://github.com/basho/faulterl", {branch, "master"}}}]}, {overrides, [{override, faulterl, [ {plugins, [pc]}, {artifacts, ["priv/faulterl.so"]}, {provider_hooks, [ {post, [{compile, {pc, compile}}, {clean, {pc, clean}}] }] } ]} ]} ]} ]}. {plugins, [pc, {eqc_rebar, {git, "https://github.com/Quviq/eqc-rebar", {branch, "master"}}}]}. {provider_hooks, [ {pre, [ {compile, {pc, compile}}, {clean, {pc, clean}} ] } ] }. {xref_checks,[undefined_function_calls,undefined_functions,locals_not_used, deprecated_function_calls, deprecated_functions]}. bitcask-2.1.0/.thumbs.yml0000644000232200023220000000020413655023466015611 0ustar debalancedebalanceminimum_reviewers: 1 build_steps: - make clean - make test - make xref - make dialyzer merge: true org_mode: true timeout: 1790 bitcask-2.1.0/c_src/0000755000232200023220000000000013655023466014603 5ustar debalancedebalancebitcask-2.1.0/c_src/murmurhash.h0000644000232200023220000000122613655023466017150 0ustar debalancedebalance/* Murmurhash from http://sites.google.com/site/murmurhash/ All code is released to the public domain. For business purposes, Murmurhash is under the MIT license. */ #ifndef MURMURHASH_H #define MURMURHASH_H #include #if defined(__x86_64__) #define MURMUR_HASH MurmurHash64A uint64_t MurmurHash64A ( const void * key, int len, unsigned int seed ); #elif defined(__i386__) #define MURMUR_HASH MurmurHash2 unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed ); #else #define MURMUR_HASH MurmurHashNeutral2 unsigned int MurmurHashNeutral2 ( const void * key, int len, unsigned int seed ); #endif #endif /* MURMURHASH_H */ bitcask-2.1.0/c_src/erl_nif_util.c0000644000232200023220000000272113655023466017424 0ustar debalancedebalance// ------------------------------------------------------------------- // // bitcask: Eric Brewer-inspired key/value store // // Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. // // This file is provided to you under the Apache License, // Version 2.0 (the "License"); you may not use this file // except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // // ------------------------------------------------------------------- #include "erl_nif_util.h" #include "erl_nif_compat.h" #include int enif_get_uint64_bin(ErlNifEnv* env, ERL_NIF_TERM term, uint64_t* outvalue) { ErlNifBinary bin; if (enif_inspect_binary(env, term, &bin) && bin.size == sizeof(uint64_t)) { memcpy(outvalue, ((uint64_t*)bin.data), sizeof(uint64_t)); return 1; } else { return 0; } } ERL_NIF_TERM enif_make_uint64_bin(ErlNifEnv* env, uint64_t value) { ErlNifBinary bin; enif_alloc_binary_compat(env, sizeof(uint64_t), &bin); memcpy(bin.data, &value, sizeof(uint64_t)); return enif_make_binary(env, &bin); } bitcask-2.1.0/c_src/erl_nif_compat.h0000644000232200023220000000410213655023466017732 0ustar debalancedebalance/* Copyright (c) 2010-2011 Basho Technologies, Inc. * * This file is provided to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #ifndef ERL_NIF_COMPAT_H_ #define ERL_NIF_COMPAT_H_ #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ #include "erl_nif.h" #if ERL_NIF_MAJOR_VERSION == 1 && ERL_NIF_MINOR_VERSION == 0 #define enif_open_resource_type_compat enif_open_resource_type #define enif_alloc_resource_compat enif_alloc_resource #define enif_release_resource_compat enif_release_resource #define enif_alloc_binary_compat enif_alloc_binary #define enif_alloc_compat enif_alloc #define enif_realloc_compat enif_realloc #define enif_free_compat enif_free #define enif_cond_create erl_drv_cond_create #define enif_cond_destroy erl_drv_cond_destroy #define enif_cond_signal erl_drv_cond_signal #define enif_cond_broadcast erl_drv_cond_broadcast #define enif_cond_wait erl_drv_cond_wait #define ErlNifCond ErlDrvCond #endif /* R13B04 */ #if ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 0 #define enif_open_resource_type_compat(E, N, D, F, T) \ enif_open_resource_type(E, NULL, N, D, F, T) #define enif_alloc_resource_compat(E, T, S) \ enif_alloc_resource(T, S) #define enif_release_resource_compat(E, H) \ enif_release_resource(H) #define enif_alloc_binary_compat(E, S, B) \ enif_alloc_binary(S, B) #define enif_alloc_compat(E, S) \ enif_alloc(S) #define enif_realloc_compat(E, P, S) \ enif_realloc(P, S) #define enif_free_compat(E, P) \ enif_free(P) #endif /* R14 */ #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* ERL_NIF_COMPAT_H_ */ bitcask-2.1.0/c_src/bitcask_nifs.c0000644000232200023220000030722413655023466017416 0ustar debalancedebalance// ------------------------------------------------------------------- // // bitcask: Eric Brewer-inspired key/value store // // Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. // // This file is provided to you under the Apache License, // Version 2.0 (the "License"); you may not use this file // except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // // ------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include "erl_nif.h" #include "erl_driver.h" #include "erl_nif_compat.h" #include "erl_nif_util.h" #include "khash.h" #include "murmurhash.h" #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" //typesystem hack to avoid some incorrect errors. typedef ErlNifUInt64 uint64; #ifdef BITCASK_DEBUG #include #include #include void DEBUG(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } int erts_snprintf(char *, size_t, const char *, ...); #define MAX_DEBUG_STR 128 #define DEBUG_STR(N, V) \ char N[MAX_DEBUG_STR];\ erts_snprintf(N, MAX_DEBUG_STR, "%s", V) #define DEBUG_BIN(N, V, S) \ char N[MAX_DEBUG_STR];\ format_bin(N, MAX_DEBUG_STR, (unsigned char*)V, (size_t)S) #define DEBUG2 DEBUG #else void DEBUG2(const char *fmt, ...) { } #define DEBUG_STR(A, B) #define DEBUG_BIN(N, V, S) # define DEBUG(X, ...) {} #endif #if defined(BITCASK_DEBUG) && defined(BITCASK_DEBUG_KEYDIR) # define DEBUG_KEYDIR(KD) print_keydir((KD)) # define DEBUG_ENTRY(E) print_entry((E)) #else # define DEBUG_KEYDIR(X) {} # define DEBUG_ENTRY(E) {} #endif #ifdef BITCASK_DEBUG void format_bin(char * buf, size_t buf_size, const unsigned char * bin, size_t bin_size) { char cbuf[4]; // up to 3 digits + \0 int is_printable = 1; int i, n; size_t av_size = buf_size; for (i=0;i>"); } else { strcat(buf, "<<"); for (i=0;i0) { strcat(buf, ","); } sprintf(cbuf, "%u", bin[i]); strcat(buf, cbuf); } strcat(buf, ">>"); } } #endif static ErlNifResourceType* bitcask_keydir_RESOURCE; static ErlNifResourceType* bitcask_lock_RESOURCE; static ErlNifResourceType* bitcask_file_RESOURCE; typedef struct { int fd; } bitcask_file_handle; typedef struct { uint32_t file_id; uint32_t total_sz; uint64_t offset; uint64_t epoch; uint32_t tstamp; uint16_t key_sz; char key[0]; } bitcask_keydir_entry; static khint_t keydir_entry_hash(bitcask_keydir_entry* entry); static khint_t keydir_entry_equal(bitcask_keydir_entry* lhs, bitcask_keydir_entry* rhs); KHASH_INIT(entries, bitcask_keydir_entry*, char, 0, keydir_entry_hash, keydir_entry_equal); typedef struct { uint32_t file_id; uint64_t live_keys; // number of 'live' keys in entries and pending uint64_t live_bytes; // number of 'live' bytes uint64_t total_keys; // total number of keys written to file uint64_t total_bytes; // total number of bytes written to file uint32_t oldest_tstamp; // oldest observed tstamp in a file uint32_t newest_tstamp; // newest observed tstamp in a file uint64_t expiration_epoch; // file obsolete at this epoch } bitcask_fstats_entry; struct bitcask_keydir_entry_sib { uint32_t file_id; uint32_t total_sz; uint64_t offset; uint64_t epoch; uint32_t tstamp; struct bitcask_keydir_entry_sib * next; }; typedef struct bitcask_keydir_entry_sib bitcask_keydir_entry_sib; typedef struct { bitcask_keydir_entry_sib * sibs; uint16_t key_sz; char key[0]; } bitcask_keydir_entry_head; // An entry pointer may be tagged to indicate it really points to an // list of entries with different timestamps. Those are created when // there are concurrent iterations and updates. #define IS_ENTRY_LIST(p) ((uint64_t)p&1) #define GET_ENTRY_LIST_POINTER(p) ((bitcask_keydir_entry_head*)((uint64_t)p&(uint64_t)~1)) #define MAKE_ENTRY_LIST_POINTER(p) ((bitcask_keydir_entry*)((uint64_t)p|(uint64_t)1)) // Holds values fetched from a regular entry or a snapshot from an entry list. typedef struct { uint32_t file_id; uint32_t total_sz; uint64_t epoch; uint64_t offset; uint32_t tstamp; uint16_t is_tombstone; uint16_t key_sz; char * key; } bitcask_keydir_entry_proxy; #define MAX_TIME ((uint32_t)-1) #define MAX_EPOCH ((uint64_t)-1) #define MAX_SIZE ((uint32_t)-1) #define MAX_FILE_ID ((uint32_t)-1) #define MAX_OFFSET ((uint64_t)-1) KHASH_MAP_INIT_INT(fstats, bitcask_fstats_entry*); typedef khash_t(entries) entries_hash_t; typedef khash_t(fstats) fstats_hash_t; typedef struct { // The hash where entries are usually stored. It may contain // regular entries or entry lists created during keyfolding. entries_hash_t* entries; // Hash used when it's not possible to update entries without // resizing it, which would break ongoing keyfolder on it. // It can only contain regular entries, not entry lists. entries_hash_t* pending; fstats_hash_t* fstats; uint64_t epoch; uint64_t key_count; uint64_t key_bytes; uint32_t biggest_file_id; unsigned int refcount; unsigned int keyfolders; uint64_t newest_folder; // Epoch for newest folder uint64_t iter_generation; char iter_mutation; // Mutation while iterating? uint64_t sweep_last_generation; // iter_generation of last sibling sweep khiter_t sweep_itr; // iterator for sibling sweep uint64_t pending_updated; uint64_t pending_start_time; // UNIX epoch seconds (since 1970) uint64_t pending_start_epoch; ErlNifPid* pending_awaken; // processes to wake once pending merged into entries unsigned int pending_awaken_count; unsigned int pending_awaken_size; ErlNifMutex* mutex; char is_ready; char name[0]; } bitcask_keydir; typedef struct { bitcask_keydir* keydir; int iterating; khiter_t iterator; uint64_t epoch; } bitcask_keydir_handle; typedef struct { int fd; int is_write_lock; char filename[0]; } bitcask_lock_handle; KHASH_INIT(global_biggest_file_id, char*, uint32_t, 1, kh_str_hash_func, kh_str_hash_equal); KHASH_INIT(global_keydirs, char*, bitcask_keydir*, 1, kh_str_hash_func, kh_str_hash_equal); typedef struct { khash_t(global_biggest_file_id)* global_biggest_file_id; khash_t(global_keydirs)* global_keydirs; ErlNifMutex* global_keydirs_lock; } bitcask_priv_data; #define kh_put2(name, h, k, v) { \ int itr_status; \ khiter_t itr = kh_put(name, h, k, &itr_status); \ kh_val(h, itr) = v; } \ #define kh_put_set(name, h, k) { \ int itr_status; \ kh_put(name, h, k, &itr_status); } // Handle lock helper functions #define LOCK(keydir) { if (keydir->mutex) enif_mutex_lock(keydir->mutex); } #define UNLOCK(keydir) { if (keydir->mutex) enif_mutex_unlock(keydir->mutex); } // Related to tombstones in the pending hash. // Notice that tombstones in the entries hash are different. #define is_pending_tombstone(e) ((e)->offset == MAX_OFFSET) #define set_pending_tombstone(e) {(e)->offset = MAX_OFFSET; } // Atoms (initialized in on_load) static ERL_NIF_TERM ATOM_ALLOCATION_ERROR; static ERL_NIF_TERM ATOM_ALREADY_EXISTS; static ERL_NIF_TERM ATOM_BITCASK_ENTRY; static ERL_NIF_TERM ATOM_ERROR; static ERL_NIF_TERM ATOM_FALSE; static ERL_NIF_TERM ATOM_FSTAT_ERROR; static ERL_NIF_TERM ATOM_FTRUNCATE_ERROR; static ERL_NIF_TERM ATOM_GETFL_ERROR; static ERL_NIF_TERM ATOM_ILT_CREATE_ERROR; /* Iteration lock thread creation error */ static ERL_NIF_TERM ATOM_ITERATION_IN_PROCESS; static ERL_NIF_TERM ATOM_ITERATION_NOT_PERMITTED; static ERL_NIF_TERM ATOM_ITERATION_NOT_STARTED; static ERL_NIF_TERM ATOM_LOCK_NOT_WRITABLE; static ERL_NIF_TERM ATOM_NOT_FOUND; static ERL_NIF_TERM ATOM_NOT_READY; static ERL_NIF_TERM ATOM_OK; static ERL_NIF_TERM ATOM_OUT_OF_DATE; static ERL_NIF_TERM ATOM_PREAD_ERROR; static ERL_NIF_TERM ATOM_PWRITE_ERROR; static ERL_NIF_TERM ATOM_READY; static ERL_NIF_TERM ATOM_SETFL_ERROR; static ERL_NIF_TERM ATOM_TRUE; static ERL_NIF_TERM ATOM_UNDEFINED; static ERL_NIF_TERM ATOM_EOF; static ERL_NIF_TERM ATOM_CREATE; static ERL_NIF_TERM ATOM_READONLY; static ERL_NIF_TERM ATOM_O_SYNC; // lseek equivalents for file_position static ERL_NIF_TERM ATOM_CUR; static ERL_NIF_TERM ATOM_BOF; // Prototypes ERL_NIF_TERM bitcask_nifs_keydir_new0(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_new1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_maybe_keydir_new1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_mark_ready(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_get_int(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_get_epoch(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_put_int(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_remove(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_copy(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_itr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_itr_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_itr_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_keydir_trim_fstats(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_increment_file_id(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_create_tmp_file(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_lock_acquire(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_lock_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_lock_readdata(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_lock_writedata(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_sync(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_pread(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_pwrite(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_read(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_position(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_seekbof(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_file_truncate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_update_fstats(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM bitcask_nifs_set_pending_delete(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); ERL_NIF_TERM errno_atom(ErlNifEnv* env, int error); ERL_NIF_TERM errno_error_tuple(ErlNifEnv* env, ERL_NIF_TERM key, int error); static void merge_pending_entries(ErlNifEnv* env, bitcask_keydir* keydir); static void lock_release(bitcask_lock_handle* handle); static void bitcask_nifs_keydir_resource_cleanup(ErlNifEnv* env, void* arg); static void bitcask_nifs_file_resource_cleanup(ErlNifEnv* env, void* arg); static ErlNifFunc nif_funcs[] = { {"keydir_new", 0, bitcask_nifs_keydir_new0}, {"keydir_new", 1, bitcask_nifs_keydir_new1}, {"maybe_keydir_new", 1, bitcask_nifs_maybe_keydir_new1}, {"keydir_mark_ready", 1, bitcask_nifs_keydir_mark_ready}, {"keydir_put_int", 10, bitcask_nifs_keydir_put_int}, {"keydir_get_int", 3, bitcask_nifs_keydir_get_int}, {"keydir_get_epoch", 1, bitcask_nifs_keydir_get_epoch}, {"keydir_remove", 3, bitcask_nifs_keydir_remove}, {"keydir_remove_int", 6, bitcask_nifs_keydir_remove}, {"keydir_copy", 1, bitcask_nifs_keydir_copy}, {"keydir_itr_int", 4, bitcask_nifs_keydir_itr}, {"keydir_itr_next_int", 1, bitcask_nifs_keydir_itr_next}, {"keydir_itr_release", 1, bitcask_nifs_keydir_itr_release}, {"keydir_info", 1, bitcask_nifs_keydir_info}, {"keydir_release", 1, bitcask_nifs_keydir_release}, {"keydir_trim_fstats", 2, bitcask_nifs_keydir_trim_fstats}, {"increment_file_id", 1, bitcask_nifs_increment_file_id}, {"increment_file_id", 2, bitcask_nifs_increment_file_id}, {"lock_acquire_int", 2, bitcask_nifs_lock_acquire}, {"lock_release_int", 1, bitcask_nifs_lock_release}, {"lock_readdata_int", 1, bitcask_nifs_lock_readdata}, {"lock_writedata_int", 2, bitcask_nifs_lock_writedata}, {"file_open_int", 2, bitcask_nifs_file_open}, {"file_close_int", 1, bitcask_nifs_file_close}, {"file_sync_int", 1, bitcask_nifs_file_sync}, {"file_pread_int", 3, bitcask_nifs_file_pread}, {"file_pwrite_int", 3, bitcask_nifs_file_pwrite}, {"file_read_int", 2, bitcask_nifs_file_read}, {"file_write_int", 2, bitcask_nifs_file_write}, {"file_position_int", 2, bitcask_nifs_file_position}, {"file_seekbof_int", 1, bitcask_nifs_file_seekbof}, {"file_truncate_int", 1, bitcask_nifs_file_truncate}, {"update_fstats", 8, bitcask_nifs_update_fstats}, {"set_pending_delete", 2, bitcask_nifs_set_pending_delete} }; ERL_NIF_TERM bitcask_nifs_keydir_new0(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { // First, setup a resource for our handle bitcask_keydir_handle* handle = enif_alloc_resource_compat(env, bitcask_keydir_RESOURCE, sizeof(bitcask_keydir_handle)); memset(handle, '\0', sizeof(bitcask_keydir_handle)); // Now allocate the actual keydir instance. Because it's unnamed/shared, we'll // leave the name and lock portions null'd out bitcask_keydir* keydir = malloc(sizeof(bitcask_keydir)); memset(keydir, '\0', sizeof(bitcask_keydir)); keydir->entries = kh_init(entries); keydir->fstats = kh_init(fstats); // Assign the keydir to our handle and hand it back handle->keydir = keydir; ERL_NIF_TERM result = enif_make_resource(env, handle); enif_release_resource_compat(env, handle); return enif_make_tuple2(env, ATOM_OK, result); } ERL_NIF_TERM bitcask_nifs_maybe_keydir_new1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { char name[4096]; if (enif_get_string(env, argv[0], name, sizeof(name), ERL_NIF_LATIN1)) { // Get our private stash and check the global hash table for this entry bitcask_priv_data* priv = (bitcask_priv_data*)enif_priv_data(env); enif_mutex_lock(priv->global_keydirs_lock); khiter_t itr = kh_get(global_keydirs, priv->global_keydirs, name); khiter_t table_end = kh_end(priv->global_keydirs); /* get end while lock is held! */ enif_mutex_unlock(priv->global_keydirs_lock); if (itr != table_end) { return bitcask_nifs_keydir_new1(env, argc, argv); } else { return enif_make_tuple2(env, ATOM_ERROR, ATOM_NOT_READY); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_new1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { char name[4096]; size_t name_sz; if (enif_get_string(env, argv[0], name, sizeof(name), ERL_NIF_LATIN1)) { name_sz = strlen(name); // Get our private stash and check the global hash table for this entry bitcask_priv_data* priv = (bitcask_priv_data*)enif_priv_data(env); enif_mutex_lock(priv->global_keydirs_lock); bitcask_keydir* keydir; khiter_t itr = kh_get(global_keydirs, priv->global_keydirs, name); if (itr != kh_end(priv->global_keydirs)) { keydir = kh_val(priv->global_keydirs, itr); // Existing keydir is available. Check the is_ready flag to determine if // the original creator is ready for other processes to use it. if (!keydir->is_ready) { // Notify the caller that while the requested keydir exists, it's not // ready for public usage. enif_mutex_unlock(priv->global_keydirs_lock); return enif_make_tuple2(env, ATOM_ERROR, ATOM_NOT_READY); } else { keydir->refcount++; } } else { // No such keydir, create a new one and add to the globals list. Make sure // to allocate enough room for the name. keydir = malloc(sizeof(bitcask_keydir) + name_sz + 1); memset(keydir, '\0', sizeof(bitcask_keydir) + name_sz + 1); strncpy(keydir->name, name, name_sz + 1); // Initialize hash tables keydir->entries = kh_init(entries); keydir->fstats = kh_init(fstats); // Be sure to initialize the mutex and set our refcount keydir->mutex = enif_mutex_create(name); keydir->refcount = 1; // Finally, register this new keydir in the globals kh_put2(global_keydirs, priv->global_keydirs, keydir->name, keydir); khiter_t itr_biggest_file_id = kh_get(global_biggest_file_id, priv->global_biggest_file_id, name); if (itr_biggest_file_id != kh_end(priv->global_biggest_file_id)) { uint32_t old_biggest_file_id = kh_val(priv->global_biggest_file_id, itr_biggest_file_id); keydir->biggest_file_id = old_biggest_file_id; } } enif_mutex_unlock(priv->global_keydirs_lock); // Setup a resource for the handle bitcask_keydir_handle* handle = enif_alloc_resource_compat(env, bitcask_keydir_RESOURCE, sizeof(bitcask_keydir_handle)); memset(handle, '\0', sizeof(bitcask_keydir_handle)); handle->keydir = keydir; ERL_NIF_TERM result = enif_make_resource(env, handle); enif_release_resource_compat(env, handle); // Return to the caller a tuple with the reference and an atom // indicating if the keydir is ready or not. ERL_NIF_TERM is_ready_atom = keydir->is_ready ? ATOM_READY : ATOM_NOT_READY; return enif_make_tuple2(env, is_ready_atom, result); } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_mark_ready(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { bitcask_keydir* keydir = handle->keydir; LOCK(keydir); keydir->is_ready = 1; UNLOCK(keydir); return ATOM_OK; } else { return enif_make_badarg(env); } } static void update_fstats(ErlNifEnv* env, bitcask_keydir* keydir, uint32_t file_id, uint32_t tstamp, uint64_t expiration_epoch, int32_t live_increment, int32_t total_increment, int32_t live_bytes_increment, int32_t total_bytes_increment, int32_t should_create) { bitcask_fstats_entry* entry = 0; khiter_t itr = kh_get(fstats, keydir->fstats, file_id); if (itr == kh_end(keydir->fstats)) { if (!should_create) { return; } // Need to initialize new entry and add to the table entry = malloc(sizeof(bitcask_fstats_entry)); memset(entry, '\0', sizeof(bitcask_fstats_entry)); entry->expiration_epoch = MAX_EPOCH; entry->file_id = file_id; kh_put2(fstats, keydir->fstats, file_id, entry); } else { entry = kh_val(keydir->fstats, itr); } entry->live_keys += live_increment; entry->total_keys += total_increment; entry->live_bytes += live_bytes_increment; entry->total_bytes += total_bytes_increment; if (expiration_epoch < entry->expiration_epoch) { entry->expiration_epoch = expiration_epoch; } if ((tstamp != 0 && tstamp < entry->oldest_tstamp) || entry->oldest_tstamp == 0) { entry->oldest_tstamp = tstamp; } if ((tstamp != 0 && tstamp > entry->newest_tstamp) || entry->newest_tstamp == 0) { entry->newest_tstamp = tstamp; } } // NIF wrapper around update_fstats(). ERL_NIF_TERM bitcask_nifs_update_fstats(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; uint32_t file_id, tstamp; int32_t live_increment, total_increment; int32_t live_bytes_increment, total_bytes_increment; int32_t should_create; if (argc == 8 && enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle) && enif_get_uint(env, argv[1], &file_id) && enif_get_uint(env, argv[2], &tstamp) && enif_get_int(env, argv[3], &live_increment) && enif_get_int(env, argv[4], &total_increment) && enif_get_int(env, argv[5], &live_bytes_increment) && enif_get_int(env, argv[6], &total_bytes_increment) && enif_get_int(env, argv[7], &should_create)) { LOCK(handle->keydir); update_fstats(env, handle->keydir, file_id, tstamp, MAX_EPOCH, live_increment, total_increment, live_bytes_increment, total_bytes_increment, should_create); UNLOCK(handle->keydir); return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_set_pending_delete(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; uint32_t file_id; if (argc == 2 && enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle) && enif_get_uint(env, argv[1], &file_id)) { LOCK(handle->keydir); update_fstats(env, handle->keydir, file_id, 0, handle->keydir->epoch, 0, 0, 0, 0, 0); UNLOCK(handle->keydir); return ATOM_OK; } else { return enif_make_badarg(env); } } static khint_t keydir_entry_hash(bitcask_keydir_entry* entry) { khint_t h; if (IS_ENTRY_LIST(entry)) { bitcask_keydir_entry_head* par = GET_ENTRY_LIST_POINTER(entry); h = MURMUR_HASH(par->key, par->key_sz, 42); } else { h = MURMUR_HASH(entry->key, entry->key_sz, 42); } return h; } static khint_t keydir_entry_equal(bitcask_keydir_entry* lhs, bitcask_keydir_entry* rhs) { char* lkey; char* rkey; int lsz, rsz; if (IS_ENTRY_LIST(lhs)) { bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(lhs); lkey = &h->key[0]; lsz = h->key_sz; } else { lkey = &lhs->key[0]; lsz = lhs->key_sz; } if (IS_ENTRY_LIST(rhs)) { bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(rhs); rkey = &h->key[0]; rsz = h->key_sz; } else { rkey = &rhs->key[0]; rsz = rhs->key_sz; } if (lsz != rsz) { return 0; } else { return (memcmp(lkey, rkey, lsz) == 0); } } // Custom hash function to be able to look up entries using a // ErlNifBinary without allocating a new entry just for that. static khint_t nif_binary_hash(void* void_bin) { ErlNifBinary * bin =(ErlNifBinary*)void_bin; return MURMUR_HASH(bin->data, bin->size, 42); } // Custom equals function to be able to look up entries using a // ErlNifBinary without allocating a new entry just for that. static khint_t nif_binary_entry_equal(bitcask_keydir_entry* lhs, void * void_rhs) { char* lkey; int lsz; if (IS_ENTRY_LIST(lhs)) { bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(lhs); lkey = &h->key[0]; lsz = h->key_sz; } else { lkey = &lhs->key[0]; lsz = lhs->key_sz; } ErlNifBinary * rhs = (ErlNifBinary*)void_rhs; if (lsz != rhs->size) { return 0; } else { return (memcmp(lkey, rhs->data, lsz) == 0); } } static khiter_t get_entries_hash(entries_hash_t *hash, ErlNifBinary* key, khiter_t* itr_ptr, bitcask_keydir_entry** entry_ptr) { khiter_t itr = kh_get_custom(entries, hash, key, nif_binary_hash, nif_binary_entry_equal); if (itr != kh_end(hash)) { if (itr_ptr != NULL) { *itr_ptr = itr; } if (entry_ptr != NULL) { *entry_ptr = kh_key(hash, itr); } return 1; } else { return 0; } } static inline int is_sib_tombstone(bitcask_keydir_entry_sib *s) { if (s->file_id == MAX_TIME && s->total_sz == MAX_SIZE && s->offset == MAX_OFFSET) { return 1; } return 0; } // Extracts the entry values from a regular entry or from the // closest snapshot in time in an entry list. static int proxy_kd_entry_at_epoch(bitcask_keydir_entry* old, uint64_t epoch, bitcask_keydir_entry_proxy * ret) { if (!IS_ENTRY_LIST(old)) { if (epoch < old->epoch) return 0; ret->file_id = old->file_id; ret->total_sz = old->total_sz; ret->offset = old->offset; ret->tstamp = old->tstamp; ret->epoch = old->epoch; ret->key_sz = old->key_sz; ret->key = old->key; ret->is_tombstone = is_pending_tombstone(old); return 1; } bitcask_keydir_entry_head* head = GET_ENTRY_LIST_POINTER(old); //grab the newest sib bitcask_keydir_entry_sib* s = head->sibs; while (s != NULL) { if (epoch >= s->epoch) { break; } s = s->next; } if (s == NULL) { return 0; } ret->file_id = s->file_id; ret->total_sz = s->total_sz; ret->offset = s->offset; ret->tstamp = s->tstamp; ret->is_tombstone = is_sib_tombstone(s); ret->epoch = s->epoch; ret->key_sz = head->key_sz; ret->key = head->key; return 1; } // Extracts entry values from a regular entry or the latest snapshot // from an entry list. static inline int proxy_kd_entry(bitcask_keydir_entry* old, bitcask_keydir_entry_proxy * proxy) { return proxy_kd_entry_at_epoch(old, MAX_EPOCH, proxy); } // All info about a lookup with find_keydir_entry. typedef struct { // Entry found in the pending hash. If set, entries_entry will be NULL. bitcask_keydir_entry * pending_entry; // Entry found in the entries hash. If set, pending_entry is NULL bitcask_keydir_entry * entries_entry; // Copy of the values of the found entry, if any, whether it's // a regular entry or list. bitcask_keydir_entry_proxy proxy; // Hash (entries or pending) where the entry was found. entries_hash_t * hash; khiter_t itr; // True if found, even if it is a tombstone char found; } find_result; // Find an entry in the pending hash when they keydir is frozen, or in the // entries hash otherwise. static void find_keydir_entry(bitcask_keydir* keydir, ErlNifBinary* key, uint64_t epoch, find_result * ret) { // Search pending. If keydir handle used is in iterating mode // we want to see a past snapshot instead. if (keydir->pending != NULL) { if (get_entries_hash(keydir->pending, key, &ret->itr, &ret->pending_entry) && (epoch >= ret->pending_entry->epoch)) { DEBUG("Found in pending %llu > %llu", epoch, ret->pending_entry->epoch); ret->hash = keydir->pending; ret->entries_entry = NULL; ret->found = 1; proxy_kd_entry(ret->pending_entry, &ret->proxy); return; } } // Definitely not in the pending entries ret->pending_entry = NULL; // If a snapshot for that time is found in regular entries if (get_entries_hash(keydir->entries, key, &ret->itr, &ret->entries_entry) && proxy_kd_entry_at_epoch(ret->entries_entry, epoch, &ret->proxy)) { ret->hash = keydir->entries; ret->found = 1; return; } ret->entries_entry = NULL; ret->hash = NULL; ret->found = 0; return; } static void update_kd_entry_list(bitcask_keydir_entry *old, bitcask_keydir_entry_proxy *new, int iterating_p) { bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(old); bitcask_keydir_entry_sib* new_sib; if (! iterating_p) { new_sib = h->sibs; new_sib->file_id = new->file_id; new_sib->total_sz = new->total_sz; new_sib->offset = new->offset; new_sib->epoch = new->epoch; new_sib->tstamp = new->tstamp; } else // otherwise make a new sib { new_sib = malloc(sizeof(bitcask_keydir_entry_sib)); new_sib->file_id = new->file_id; new_sib->total_sz = new->total_sz; new_sib->offset = new->offset; new_sib->epoch = new->epoch; new_sib->tstamp = new->tstamp; new_sib->next = h->sibs; h->sibs = new_sib; } } static bitcask_keydir_entry* new_kd_entry_list(bitcask_keydir_entry *old, bitcask_keydir_entry_proxy *new) { bitcask_keydir_entry_head* ret; bitcask_keydir_entry_sib *old_sib, *new_sib; ret = malloc(sizeof(bitcask_keydir_entry_head) + old->key_sz); old_sib = malloc(sizeof(bitcask_keydir_entry_sib)); new_sib = malloc(sizeof(bitcask_keydir_entry_sib)); //fill in list head, use old since new could be a tombstone memcpy(ret->key, old->key, old->key_sz); ret->key_sz = old->key_sz; ret->sibs = new_sib; //make new sib new_sib->file_id = new->file_id; new_sib->total_sz = new->total_sz; new_sib->offset = new->offset; new_sib->epoch = new->epoch; new_sib->tstamp = new->tstamp; new_sib->next = old_sib; //make new sib old_sib->file_id = old->file_id; old_sib->total_sz = old->total_sz; old_sib->offset = old->offset; old_sib->epoch = old->epoch; old_sib->tstamp = old->tstamp; old_sib->next = NULL; return MAKE_ENTRY_LIST_POINTER(ret); } #ifdef BITCASK_DEBUG void print_entry_list(bitcask_keydir_entry *e) { bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(e); char buf[4096]; assert(h->key_sz+1 < 4096); memcpy(&buf, h->key, h->key_sz); buf[h->key_sz] = '\0'; fprintf(stderr, "entry list %p key: %s keylen %d\r\n", h, buf, h->key_sz); int sib_count = 0; bitcask_keydir_entry_sib *s = h->sibs; while (s != NULL) { fprintf(stderr, "sib %d \r\n\t%u\t\t%u\r\n\t%llu\t\t%u\tepoch=%u\r\n\r\n", sib_count, s->file_id, s->total_sz, (unsigned long long)s->offset, s->tstamp, s->epoch); sib_count++; s = s->next; if( s == NULL ) break; } } void print_entry(bitcask_keydir_entry *e) { if (IS_ENTRY_LIST(e)) { print_entry_list(e); return; } fprintf(stderr, "entry %p key: %d keylen %d\r\n", e, (int)e->key[3], e->key_sz); fprintf(stderr, "\r\n\t%u\t\t%u\r\n\t%llu\t\t%u\tepoch=%u\r\n\r\n", e->file_id, e->total_sz, (unsigned long long)e->offset, e->tstamp, e->epoch); } void print_keydir(bitcask_keydir* keydir) { khiter_t itr; bitcask_keydir_entry* current_entry; fprintf(stderr, "printing keydir: %s size %d\r\n\r\n", keydir->name, kh_size(keydir->entries)); // should likely dump some useful stuff here, but don't need it // right now fprintf(stderr, "entries:\r\n"); for (itr = kh_begin(keydir->entries); itr != kh_end(keydir->entries); ++itr) { if (kh_exist(keydir->entries, itr)) { current_entry = kh_key(keydir->entries, itr); print_entry(current_entry); } } fprintf(stderr, "\r\npending:\r\n"); if (keydir->pending == NULL) { fprintf(stderr, "NULL\r\n"); } else { for (itr = kh_begin(keydir->pending); itr != kh_end(keydir->pending); ++itr) { if (kh_exist(keydir->pending, itr)) { current_entry = kh_key(keydir->pending, itr); print_entry(current_entry); } } } } #endif static void free_entry_list(bitcask_keydir_entry* e) { bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(e); bitcask_keydir_entry_sib *temp = NULL, *s = h->sibs; while (s != NULL) { temp = s; s = s->next; free(temp); } free(h); } static void free_entry(bitcask_keydir_entry *e) { if (IS_ENTRY_LIST(e)) { free_entry_list(e); } else { free(e); } } // Allocate, populate and add entry to the keydir hash based on the key and entry structure // never need to add an entry list, can update to it later. static bitcask_keydir_entry* add_entry(bitcask_keydir* keydir, entries_hash_t* hash, bitcask_keydir_entry_proxy * entry) { bitcask_keydir_entry* new_entry = malloc(sizeof(bitcask_keydir_entry) + entry->key_sz); new_entry->file_id = entry->file_id; new_entry->total_sz = entry->total_sz; new_entry->offset = entry->offset; new_entry->epoch = entry->epoch; new_entry->tstamp = entry->tstamp; new_entry->key_sz = entry->key_sz; memcpy(new_entry->key, entry->key, entry->key_sz); kh_put_set(entries, hash, new_entry); return new_entry; } static void update_regular_entry(bitcask_keydir_entry* cur_entry, bitcask_keydir_entry_proxy* upd_entry) { cur_entry->file_id = upd_entry->file_id; cur_entry->total_sz = upd_entry->total_sz; cur_entry->epoch = upd_entry->epoch; cur_entry->offset = upd_entry->offset; cur_entry->tstamp = upd_entry->tstamp; } // Updates an entry from the entries hash, not from pending. // Use update_regular_entry on pending hash entries instead. // While iterating, regular entries will become entry lists, // otherwise the result is a regular, single value entry. static void update_entry(bitcask_keydir* keydir, bitcask_keydir_entry* cur_entry, bitcask_keydir_entry_proxy* upd_entry) { int is_entry_list = IS_ENTRY_LIST(cur_entry); int iterating = keydir->keyfolders > 0; if (iterating) { if (is_entry_list) { // Add to list of values during iteration update_kd_entry_list(cur_entry, upd_entry, iterating); } else { // Convert regular entry to list during iteration khiter_t itr = kh_get(entries, keydir->entries, cur_entry); kh_key(keydir->entries, itr) = new_kd_entry_list(cur_entry, upd_entry); free(cur_entry); } } else // not iterating, so end up with regular entries only. { if (is_entry_list) { // Convert list to regular entry khiter_t itr = kh_get(entries, keydir->entries, cur_entry); bitcask_keydir_entry_head* h = GET_ENTRY_LIST_POINTER(cur_entry); bitcask_keydir_entry* new_entry = malloc(sizeof(bitcask_keydir_entry) + h->key_sz); new_entry->file_id = upd_entry->file_id; new_entry->total_sz = upd_entry->total_sz; new_entry->offset = upd_entry->offset; new_entry->epoch = upd_entry->epoch; new_entry->tstamp = upd_entry->tstamp; new_entry->key_sz = h->key_sz; memcpy(new_entry->key, h->key, h->key_sz); kh_key(keydir->entries, itr) = new_entry; free_entry_list(cur_entry); } else // regular entry, no iteration { update_regular_entry(cur_entry, upd_entry); } } } // Remove entry from either hash and free its memory. static void remove_entry(bitcask_keydir* keydir, khiter_t itr) { bitcask_keydir_entry * entry = kh_key(keydir->entries, itr); kh_del(entries, keydir->entries, itr); free_entry(entry); } static void perhaps_sweep_siblings(bitcask_keydir* keydir) { int i; bitcask_keydir_entry* current_entry; bitcask_keydir_entry_proxy proxy; struct timeval target, now; suseconds_t max_usec = 600; assert(keydir != NULL); /* fprintf(stderr, "keydir iter_mutation %d sweep_last_generation %d iter_generation %d\r\n", keydir->iter_mutation,keydir->sweep_last_generation,keydir->iter_generation); */ if (keydir->keyfolders > 0 || keydir->iter_mutation == 0 || keydir->sweep_last_generation == keydir->iter_generation) { return; } gettimeofday(&target, NULL); target.tv_usec += max_usec; if (target.tv_usec > 1000000) { target.tv_sec++; target.tv_usec = target.tv_usec % 1000000; } while (i--) { if ((i % 500) == 0) { gettimeofday(&now, NULL); if (now.tv_sec > target.tv_usec && now.tv_usec > target.tv_usec) { break; } } if (keydir->sweep_itr >= kh_end(keydir->entries)) { keydir->sweep_itr = kh_begin(keydir->entries); keydir->sweep_last_generation = keydir->iter_generation; return; } if (kh_exist(keydir->entries, keydir->sweep_itr)) { current_entry = kh_key(keydir->entries, keydir->sweep_itr); if (IS_ENTRY_LIST(current_entry)) { if (proxy_kd_entry(current_entry, &proxy)) { if (proxy.is_tombstone) { remove_entry(keydir, keydir->sweep_itr); } else { update_entry(keydir, current_entry, &proxy); } } } } keydir->sweep_itr++; } } // Adds a tombstone to an existing entries hash entry. Regular entries are // converted to lists first. Only to be called during iterations. // Entries are simply removed when there are no iterations. static void set_entry_tombstone(bitcask_keydir* keydir, khiter_t itr, uint32_t remove_time, uint64_t remove_epoch) { bitcask_keydir_entry_proxy tombstone; tombstone.tstamp = remove_time; tombstone.epoch = remove_epoch; tombstone.offset = MAX_OFFSET; tombstone.total_sz = MAX_SIZE; tombstone.file_id = MAX_FILE_ID; tombstone.key_sz = 0; bitcask_keydir_entry * entry= kh_key(keydir->entries, itr); if (!IS_ENTRY_LIST(entry)) { // update into an entry list bitcask_keydir_entry* new_entry_list; new_entry_list = new_kd_entry_list(entry, &tombstone); kh_key(keydir->entries, itr) = new_entry_list; free(entry); } else { //need to update the entry list with a tombstone update_kd_entry_list(entry, &tombstone, keydir->keyfolders > 0); } } // Adds or updates an entry in the pending hash if they keydir is frozen // or in the entries hash otherwise. static void put_entry(bitcask_keydir * keydir, find_result * r, bitcask_keydir_entry_proxy * entry) { // found in pending (keydir is frozen), update that one if (r->pending_entry) { update_regular_entry(r->pending_entry, entry); } // iterating (frozen) and not found in pending, add to pending else if (keydir->pending) { add_entry(keydir, keydir->pending, entry); keydir->pending_updated++; } // found in entries, update that one else if (r->entries_entry) { update_entry(keydir, r->entries_entry, entry); } // Not found and not frozen, add to entries else { add_entry(keydir, keydir->entries, entry); } if (entry->file_id > keydir->biggest_file_id) { keydir->biggest_file_id = entry->file_id; } } ERL_NIF_TERM bitcask_nifs_keydir_put_int(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; bitcask_keydir_entry_proxy entry; ErlNifBinary key; uint32_t nowsec; uint32_t newest_put; uint32_t old_file_id; uint64_t old_offset; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle) && enif_inspect_binary(env, argv[1], &key) && enif_get_uint(env, argv[2], &(entry.file_id)) && enif_get_uint(env, argv[3], &(entry.total_sz)) && enif_get_uint64_bin(env, argv[4], &(entry.offset)) && enif_get_uint(env, argv[5], &(entry.tstamp)) && enif_get_uint(env, argv[6], &(nowsec)) && enif_get_uint(env, argv[7], &(newest_put)) && enif_get_uint(env, argv[8], &(old_file_id)) && enif_get_uint64_bin(env, argv[9], &(old_offset))) { bitcask_keydir* keydir = handle->keydir; entry.key = (char*)key.data; entry.key_sz = key.size; LOCK(keydir); DEBUG2("LINE %d put\r\n", __LINE__); DEBUG_BIN(dbgKey, key.data, key.size); DEBUG("+++ Put key = %s file_id=%d offset=%d total_sz=%d tstamp=%u old_file_id=%d\r\n", dbgKey, (int) entry.file_id, (int) entry.offset, (int)entry.total_sz, (unsigned) entry.tstamp, (int)old_file_id); DEBUG_KEYDIR(keydir); perhaps_sweep_siblings(handle->keydir); find_result f; find_keydir_entry(keydir, &key, MAX_EPOCH, &f); // If conditional put and not found, bail early if ((!f.found || f.proxy.is_tombstone) && old_file_id != 0) { DEBUG2("LINE %d put -> already_exists\r\n", __LINE__); UNLOCK(keydir); return ATOM_ALREADY_EXISTS; } keydir->epoch += 1; //don't worry about backing this out if we bail entry.epoch = keydir->epoch; // If put would resize and iterating, start pending hash if (kh_put_will_resize(entries, keydir->entries) && keydir->keyfolders != 0 && (keydir->pending == NULL)) { keydir->pending = kh_init(entries); keydir->pending_start_epoch = keydir->epoch; keydir->pending_start_time = nowsec; } if (!f.found || f.proxy.is_tombstone) { if ((newest_put && (entry.file_id < keydir->biggest_file_id)) || old_file_id != 0) { /* * Really, it doesn't exist. But the atom 'already_exists' * is also a signal that a merge has incremented the * keydir->biggest_file_id and that we need to retry this * operation after Erlang-land has re-written the key & val * to a new location in the same-or-bigger file id. */ DEBUG2("LINE %d put -> already_exists\r\n", __LINE__); UNLOCK(keydir); return ATOM_ALREADY_EXISTS; } keydir->key_count++; keydir->key_bytes += key.size; if (keydir->keyfolders > 0) { keydir->iter_mutation = 1; } // Increment live and total stats. update_fstats(env, keydir, entry.file_id, entry.tstamp, MAX_EPOCH, 1, 1, entry.total_sz, entry.total_sz, 1); put_entry(keydir, &f, &entry); DEBUG("+++ Put new\r\n"); DEBUG_KEYDIR(keydir); DEBUG2("LINE %d put -> ok (!found || !tombstone)\r\n", __LINE__); UNLOCK(keydir); return ATOM_OK; } // Putting only if replacing this file/offset entry, fail otherwise. // This is an important part of our optimistic concurrency mechanisms // to resolve races between writers (main and merge currently). if (old_file_id != 0 && // This line is tricky: We are trying to detect a merge putting // a value that replaces another value that same merge just put // (so same output file). Because when it does that, it has // replaced a previous value with smaller file/offset. It then // found yet another value that is also current and should // be written to the merge file, but since it has smaller file/ofs // than the newly merged value (in a new merge file), it is // ignored. This happens with values from the same second, // since the out of date logic in merge uses timestamps. (newest_put || entry.file_id != f.proxy.file_id) && !(old_file_id == f.proxy.file_id && old_offset == f.proxy.offset)) { DEBUG("++ Conditional not match\r\n"); DEBUG2("LINE %d put -> already_exists/cond bad match\r\n", __LINE__); UNLOCK(keydir); return ATOM_ALREADY_EXISTS; } // Avoid updating with stale data. Allow if: // - If real put to current write file, not a stale one // - If internal put (from merge, etc) with newer timestamp // - If internal put with a higher file id or higher offset if ((newest_put && (entry.file_id >= keydir->biggest_file_id)) || (! newest_put && (f.proxy.tstamp < entry.tstamp)) || (! newest_put && ((f.proxy.file_id < entry.file_id) || (((f.proxy.file_id == entry.file_id) && (f.proxy.offset < entry.offset)))))) { if (keydir->keyfolders > 0) { keydir->iter_mutation = 1; } // Remove the stats for the old entry and add the new if (f.proxy.file_id != entry.file_id) // different files { update_fstats(env, keydir, f.proxy.file_id, 0, MAX_EPOCH, -1, 0, -f.proxy.total_sz, 0, 0); update_fstats(env, keydir, entry.file_id, entry.tstamp, MAX_EPOCH, 1, 1, entry.total_sz, entry.total_sz, 1); } else // file_id is same, change live/total in one entry { update_fstats(env, keydir, entry.file_id, entry.tstamp, MAX_EPOCH, 0, 1, entry.total_sz - f.proxy.total_sz, entry.total_sz, 1); } put_entry(keydir, &f, &entry); DEBUG2("LINE %d put -> ok\r\n", __LINE__); UNLOCK(keydir); DEBUG("Finished put\r\n"); DEBUG_KEYDIR(keydir); return ATOM_OK; } else { // If not live yet, live stats are not updated, but total stats are if (!keydir->is_ready) { update_fstats(env, keydir, entry.file_id, entry.tstamp, MAX_EPOCH, 0, 1, 0, entry.total_sz, 1); } DEBUG2("LINE %d put -> already_exists end\r\n", __LINE__); UNLOCK(keydir); DEBUG("No update\r\n"); return ATOM_ALREADY_EXISTS; } } else { return enif_make_badarg(env); } } /* int erts_printf(const char *, ...); */ ERL_NIF_TERM bitcask_nifs_keydir_get_int(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; ErlNifBinary key; uint64 epoch; //intentionally odd type to get around warnings if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle) && enif_inspect_binary(env, argv[1], &key) && enif_get_uint64(env, argv[2], &epoch)) { bitcask_keydir* keydir = handle->keydir; LOCK(keydir); DEBUG_BIN(dbgKey, key.data, key.size); DEBUG("+++ Get %s time = %lu\r\n", dbgKey, epoch); perhaps_sweep_siblings(handle->keydir); find_result f; find_keydir_entry(keydir, &key, epoch, &f); if (f.found && !f.proxy.is_tombstone) { ERL_NIF_TERM result; result = enif_make_tuple6(env, ATOM_BITCASK_ENTRY, argv[1], /* Key */ enif_make_uint(env, f.proxy.file_id), enif_make_uint(env, f.proxy.total_sz), enif_make_uint64_bin(env, f.proxy.offset), enif_make_uint(env, f.proxy.tstamp)); DEBUG(" ... returned value file id=%u size=%u ofs=%u tstamp=%u tomb=%u\r\n", f.proxy.file_id, f.proxy.total_sz, f.proxy.offset, f.proxy.tstamp, (unsigned)f.proxy.is_tombstone); DEBUG_ENTRY(f.entries_entry ? f.entries_entry : f.pending_entry); UNLOCK(keydir); return result; } else { DEBUG(" ... not_found\r\n"); UNLOCK(keydir); return ATOM_NOT_FOUND; } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_get_epoch(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { LOCK(handle->keydir); uint64 epoch = handle->keydir->epoch; UNLOCK(handle->keydir); return enif_make_uint64(env, epoch); } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_remove(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; ErlNifBinary key; uint32_t tstamp; uint32_t file_id; uint64_t offset; uint32_t remove_time; // If this call has 6 arguments, this is a conditional removal. We // only want to actually remove the entry if the tstamp, fileid and // offset matches the one provided. A sort of poor-man's CAS. int is_conditional = argc == 6; int common_args_ok = enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle) && enif_inspect_binary(env, argv[1], &key); int other_args_ok = is_conditional ? (enif_get_uint(env, argv[2], (unsigned int*)&tstamp) && enif_get_uint(env, argv[3], (unsigned int*)&file_id) && enif_get_uint64_bin(env, argv[4], (uint64_t*)&offset) && enif_get_uint(env, argv[5], &remove_time)) : ( enif_get_uint(env, argv[2], &remove_time)); if (common_args_ok && other_args_ok) { bitcask_keydir* keydir = handle->keydir; LOCK(keydir); keydir->epoch += 1; // never back out, even if we don't mutate DEBUG("+++ Remove %s\r\n", is_conditional ? "conditional" : ""); DEBUG_KEYDIR(keydir); perhaps_sweep_siblings(handle->keydir); find_result fr; find_keydir_entry(keydir, &key, keydir->epoch, &fr); if (fr.found && !fr.proxy.is_tombstone) { // If a conditional remove, bail if not a match. if (is_conditional && (fr.proxy.tstamp != tstamp || fr.proxy.file_id != file_id || fr.proxy.offset != offset)) { UNLOCK(keydir); DEBUG("+++Conditional no match\r\n"); return ATOM_ALREADY_EXISTS; } // Remove the key from the keydir stats keydir->key_count--; keydir->key_bytes -= fr.proxy.key_sz; if (keydir->keyfolders > 0) { keydir->iter_mutation = 1; } // Remove from file stats update_fstats(env, keydir, fr.proxy.file_id, fr.proxy.tstamp, MAX_EPOCH, -1, 0, -fr.proxy.total_sz, 0, 0); // If found an entry in the pending hash, convert it to a tombstone if (fr.pending_entry) { DEBUG2("LINE %d pending put\r\n", __LINE__); set_pending_tombstone(fr.pending_entry); fr.pending_entry->tstamp = remove_time; fr.pending_entry->epoch = keydir->epoch; } // If frozen, add tombstone to pending hash (iteration must have // started between put/remove call in bitcask:delete. else if (keydir->pending) { DEBUG2("LINE %d pending put\r\n", __LINE__); bitcask_keydir_entry* pending_entry = add_entry(keydir, keydir->pending, &fr.proxy); set_pending_tombstone(pending_entry); pending_entry->tstamp = remove_time; pending_entry->epoch = keydir->epoch; } // If not iterating, just remove. else if(keydir->keyfolders == 0) { remove_entry(keydir, fr.itr); } // else found in entries while iterating else { set_entry_tombstone(keydir, fr.itr, remove_time, keydir->epoch); } DEBUG("Removed\r\n"); DEBUG_KEYDIR(keydir); UNLOCK(keydir); return ATOM_OK;; } else // not found { DEBUG("Not found - not removed\r\n"); UNLOCK(keydir); return ATOM_OK;; } } // if args OK return enif_make_badarg(env); } bitcask_keydir_entry * clone_entry(bitcask_keydir_entry * curr) { if (IS_ENTRY_LIST(curr)) { bitcask_keydir_entry_head * curr_head = GET_ENTRY_LIST_POINTER(curr); size_t head_sz = sizeof(bitcask_keydir_entry_head) + curr->key_sz; bitcask_keydir_entry_head * new_head = malloc(head_sz); memcpy(new_head, curr_head, head_sz); bitcask_keydir_entry_sib ** sib_ptr = &new_head->sibs; bitcask_keydir_entry_sib * next_sib = curr_head->sibs; while (next_sib) { bitcask_keydir_entry_sib * sib = malloc(sizeof(bitcask_keydir_entry_sib)); memcpy(sib, next_sib, sizeof(bitcask_keydir_entry_sib)); *sib_ptr = sib; sib_ptr = &sib->next; next_sib = next_sib->next; } *sib_ptr = NULL; return MAKE_ENTRY_LIST_POINTER(new_head); } else { size_t new_sz = sizeof(bitcask_keydir_entry) + curr->key_sz; bitcask_keydir_entry* new = malloc(new_sz); memcpy(new, curr, new_sz); return curr; } } ERL_NIF_TERM bitcask_nifs_keydir_copy(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { bitcask_keydir* keydir = handle->keydir; LOCK(keydir); bitcask_keydir_handle* new_handle = enif_alloc_resource_compat(env, bitcask_keydir_RESOURCE, sizeof(bitcask_keydir_handle)); memset(handle, '\0', sizeof(bitcask_keydir_handle)); // Now allocate the actual keydir instance. Because it's unnamed/shared, we'll // leave the name and lock portions null'd out bitcask_keydir* new_keydir = malloc(sizeof(bitcask_keydir)); new_handle->keydir = new_keydir; memset(new_keydir, '\0', sizeof(bitcask_keydir)); new_keydir->entries = kh_init(entries); new_keydir->fstats = kh_init(fstats); // Deep copy each item from the existing handle khiter_t itr; for (itr = kh_begin(keydir->entries); itr != kh_end(keydir->entries); ++itr) { // Allocate our entry to be inserted into the new table and copy the record // over. if (kh_exist(keydir->entries, itr)) { bitcask_keydir_entry* curr = kh_key(keydir->entries, itr); bitcask_keydir_entry* new = clone_entry(curr); kh_put_set(entries, new_keydir->entries, new); } } if (keydir->pending != NULL) { DEBUG2("LINE %d pending copy\r\n", __LINE__); for (itr = kh_begin(keydir->pending); itr != kh_end(keydir->pending); ++itr) { // Allocate our entry to be inserted into the new table and copy the record // over. if (kh_exist(keydir->pending, itr)) { bitcask_keydir_entry* curr = kh_key(keydir->pending, itr); bitcask_keydir_entry* new = clone_entry(curr); kh_put_set(entries, new_keydir->pending, new); } } } // Deep copy fstats info for (itr = kh_begin(keydir->fstats); itr != kh_end(keydir->fstats); ++itr) { if (kh_exist(keydir->fstats, itr)) { bitcask_fstats_entry* curr_f = kh_val(keydir->fstats, itr); bitcask_fstats_entry* new_f = malloc(sizeof(bitcask_fstats_entry)); memcpy(new_f, curr_f, sizeof(bitcask_fstats_entry)); kh_put2(fstats, new_keydir->fstats, new_f->file_id, new_f); } } UNLOCK(keydir); ERL_NIF_TERM result = enif_make_resource(env, new_handle); enif_release_resource_compat(env, new_handle); return enif_make_tuple2(env, ATOM_OK, result); } else { return enif_make_badarg(env); } } // Helper for bitcask_nifs_keydir_itr to decide if it is valid to iterate over entries. // Check the number of updates since pending was created is less than the maximum // and that the current view is not too old. // Call with ts set to zero to force a wait on any pending keydir. // Set maxage or maxputs negative to ignore them. Set both negative to force // using the keydir - useful when a process has waited once and needs to run // next time. static int can_itr_keydir(bitcask_keydir* keydir, uint32_t ts, int maxage, int maxputs) { if (keydir->pending == NULL || // not frozen or caller wants to reuse (maxage < 0 && maxputs < 0)) // the exiting freeze { DEBUG2("LINE %d can_itr\r\n", __LINE__); return 1; } else if (ts == 0 || ts < keydir->pending_start_time) { // if clock skew (or forced wait), force key folding to wait DEBUG2("LINE %d can_itr\r\n", __LINE__); return 0; // which will fix keydir->pending_start } { DEBUG2("LINE %d can_itr\r\n", __LINE__); uint64_t age = ts - keydir->pending_start_time; return ((maxage < 0 || age <= maxage) && (maxputs < 0 || keydir->pending_updated <= maxputs)); } } ERL_NIF_TERM bitcask_nifs_keydir_itr(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { uint32_t ts; int maxage; int maxputs; LOCK(handle->keydir); DEBUG("+++ itr\r\n"); bitcask_keydir* keydir = handle->keydir; // If a iterator thread is already active for this keydir, bail if (handle->iterating) { UNLOCK(handle->keydir); return enif_make_tuple2(env, ATOM_ERROR, ATOM_ITERATION_IN_PROCESS); } if (!(enif_get_uint(env, argv[1], &ts) && enif_get_int(env, argv[2], (int*)&maxage) && enif_get_int(env, argv[3], (int*)&maxputs))) { UNLOCK(handle->keydir); return enif_make_badarg(env); } if (can_itr_keydir(keydir, ts, maxage, maxputs)) { keydir->epoch += 1; handle->iterating = 1; handle->epoch = keydir->epoch; keydir->newest_folder = keydir->epoch; keydir->keyfolders++; handle->iterator = kh_begin(keydir->entries); DEBUG2("LINE %d itr started, keydir->pending = 0x%lx\r\n", __LINE__, keydir->pending); UNLOCK(handle->keydir); return ATOM_OK; } else { // Grow the pending_awaken array if necessary if (keydir->pending_awaken_count == keydir->pending_awaken_size) { // Grow 16-at-a-time, expect a single alloc keydir->pending_awaken_size += 16; size_t size = keydir->pending_awaken_size * sizeof(keydir->pending_awaken[0]); if (keydir->pending_awaken == NULL) { keydir->pending_awaken = malloc(size); } else { keydir->pending_awaken = realloc(keydir->pending_awaken, size); } } enif_self(env, &keydir->pending_awaken[keydir->pending_awaken_count]); keydir->pending_awaken_count++; DEBUG2("LINE %d itr\r\n", __LINE__); UNLOCK(handle->keydir); return ATOM_OUT_OF_DATE; } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_itr_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { DEBUG("+++ itr next\r\n"); bitcask_keydir* keydir = handle->keydir; if (handle->iterating != 1) { DEBUG("Itr not started\r\n"); // Iteration not started! return enif_make_tuple2(env, ATOM_ERROR, ATOM_ITERATION_NOT_STARTED); } LOCK(keydir); while (handle->iterator != kh_end(keydir->entries)) { if (kh_exist(keydir->entries, handle->iterator)) { DEBUG2("LINE %d itr_next\r\n", __LINE__); bitcask_keydir_entry* entry = kh_key(keydir->entries, handle->iterator); ErlNifBinary key; bitcask_keydir_entry_proxy proxy; if (!proxy_kd_entry_at_epoch(entry, handle->epoch, &proxy) || proxy.is_tombstone) { DEBUG("No value for itr_next"); // No value in the snapshot for the iteration time (handle->iterator)++; continue; } DEBUG_BIN(dbgKey, proxy.key, proxy.key_sz); DEBUG("itr_next key=%s", dbgKey); // Alloc the binary and make sure it succeeded if (!enif_alloc_binary_compat(env, proxy.key_sz, &key)) { UNLOCK(keydir); return ATOM_ALLOCATION_ERROR; } // Copy the data from our key to the new allocated binary // TODO: If we maintained a ErlNifBinary in the original entry, could we // get away with not doing a copy here? memcpy(key.data, proxy.key, proxy.key_sz); ERL_NIF_TERM curr = enif_make_tuple6(env, ATOM_BITCASK_ENTRY, enif_make_binary(env, &key), enif_make_uint(env, proxy.file_id), enif_make_uint(env, proxy.total_sz), enif_make_uint64_bin(env, proxy.offset), enif_make_uint(env, proxy.tstamp)); // Update the iterator to the next entry (handle->iterator)++; UNLOCK(keydir); DEBUG("Found entry\r\n"); DEBUG_ENTRY(entry); return curr; } else { // No item in this slot; increment the iterator and keep looping (handle->iterator)++; } } UNLOCK(keydir); // The iterator is at the end of the table return ATOM_NOT_FOUND; } else { return enif_make_badarg(env); } } void itr_release_internal(ErlNifEnv* env, bitcask_keydir_handle* handle) { handle->iterating = 0; handle->keydir->keyfolders--; handle->epoch = MAX_EPOCH; // If last iterator closing, unfreeze keydir and merge pending entries. if (handle->keydir->keyfolders == 0 && handle->keydir->pending != NULL) { DEBUG2("LINE %d itr_release\r\n", __LINE__); merge_pending_entries(env, handle->keydir); handle->keydir->iter_generation++; } } ERL_NIF_TERM bitcask_nifs_keydir_itr_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { LOCK(handle->keydir); if (handle->iterating != 1) { // Iteration not started! UNLOCK(handle->keydir); return enif_make_tuple2(env, ATOM_ERROR, ATOM_ITERATION_NOT_STARTED); } itr_release_internal(env, handle); UNLOCK(handle->keydir); return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { bitcask_keydir* keydir = handle->keydir; if (keydir == NULL) { return enif_make_badarg(env); } LOCK(keydir); // Dump fstats info into a list of [{file_id, live_keys, total_keys, // live_bytes, total_bytes, // oldest_tstamp, newest_tstamp, // expiration_epoch}] ERL_NIF_TERM fstats_list = enif_make_list(env, 0); khiter_t itr; bitcask_fstats_entry* curr_f; for (itr = kh_begin(keydir->fstats); itr != kh_end(keydir->fstats); ++itr) { if (kh_exist(keydir->fstats, itr)) { curr_f = kh_val(keydir->fstats, itr); ERL_NIF_TERM fstat = enif_make_tuple8(env, enif_make_uint(env, curr_f->file_id), enif_make_ulong(env, curr_f->live_keys), enif_make_ulong(env, curr_f->total_keys), enif_make_ulong(env, curr_f->live_bytes), enif_make_ulong(env, curr_f->total_bytes), enif_make_uint(env, curr_f->oldest_tstamp), enif_make_uint(env, curr_f->newest_tstamp), enif_make_uint64(env, (ErlNifUInt64)curr_f->expiration_epoch)); fstats_list = enif_make_list_cell(env, fstat, fstats_list); } } ERL_NIF_TERM iter_info = enif_make_tuple4(env, enif_make_uint64(env, keydir->iter_generation), enif_make_ulong(env, keydir->keyfolders), keydir->pending == NULL ? ATOM_FALSE : ATOM_TRUE, keydir->pending == NULL ? ATOM_UNDEFINED : enif_make_uint64(env, keydir->pending_start_epoch)); ERL_NIF_TERM result = enif_make_tuple5(env, enif_make_uint64(env, keydir->key_count), enif_make_uint64(env, keydir->key_bytes), fstats_list, iter_info, enif_make_uint64(env, keydir->epoch)); UNLOCK(keydir); return result; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { bitcask_nifs_keydir_resource_cleanup(env, handle); return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_increment_file_id(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; uint32_t conditional_file_id = 0; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)) { if (argc == 2) { enif_get_uint(env, argv[1], &(conditional_file_id)); } LOCK(handle->keydir); if (conditional_file_id == 0) { (handle->keydir->biggest_file_id)++; } else { if (conditional_file_id > handle->keydir->biggest_file_id) { handle->keydir->biggest_file_id = conditional_file_id; } } uint32_t id = handle->keydir->biggest_file_id; UNLOCK(handle->keydir); return enif_make_tuple2(env, ATOM_OK, enif_make_uint(env, id)); } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_keydir_trim_fstats(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_keydir_handle* handle; ERL_NIF_TERM head, tail, list; uint32_t non_existent_entries = 0; if (enif_get_resource(env, argv[0], bitcask_keydir_RESOURCE, (void**)&handle)&& enif_is_list(env, argv[1])) { bitcask_keydir* keydir = handle->keydir; LOCK(keydir); uint32_t file_id; list = argv[1]; while (enif_get_list_cell(env, list, &head, &tail)) { enif_get_uint(env, head, &file_id); khiter_t itr = kh_get(fstats, keydir->fstats, file_id); if (itr != kh_end(keydir->fstats)) { bitcask_fstats_entry* curr_f; curr_f = kh_val(keydir->fstats, itr); free(curr_f); kh_del(fstats, keydir->fstats, itr); } else { non_existent_entries++; } // if not found, noop, but shouldn't happen. // think about chaning the retval to signal for warning? list = tail; } UNLOCK(keydir); return enif_make_tuple2(env, ATOM_OK, enif_make_uint(env, non_existent_entries)); } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_lock_acquire(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { char filename[4096]; int is_write_lock = 0; if (enif_get_string(env, argv[0], filename, sizeof(filename), ERL_NIF_LATIN1) > 0 && enif_get_int(env, argv[1], &is_write_lock)) { // Setup the flags for the lock file int flags = O_RDONLY; if (is_write_lock) { // Use O_SYNC (in addition to other flags) to ensure that when we write // data to the lock file it is immediately (or nearly) available to any // other reading processes flags = O_CREAT | O_EXCL | O_RDWR | O_SYNC; } // Try to open the lock file -- allocate a resource if all goes well. int fd = open(filename, flags, 0600); if (fd > -1) { // Successfully opened the file -- setup a resource to track the FD. unsigned int filename_sz = strlen(filename) + 1; bitcask_lock_handle* handle = enif_alloc_resource_compat(env, bitcask_lock_RESOURCE, sizeof(bitcask_lock_handle) + filename_sz); handle->fd = fd; handle->is_write_lock = is_write_lock; strncpy(handle->filename, filename, filename_sz); ERL_NIF_TERM result = enif_make_resource(env, handle); enif_release_resource_compat(env, handle); return enif_make_tuple2(env, ATOM_OK, result); } else { return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_lock_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_lock_handle* handle; if (enif_get_resource(env, argv[0], bitcask_lock_RESOURCE, (void**)&handle)) { lock_release(handle); return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_lock_readdata(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_lock_handle* handle; if (enif_get_resource(env, argv[0], bitcask_lock_RESOURCE, (void**)&handle)) { // Stat the filehandle so we can read the entire contents into memory struct stat sinfo; if (fstat(handle->fd, &sinfo) != 0) { return errno_error_tuple(env, ATOM_FSTAT_ERROR, errno); } // Allocate a binary to hold the contents of the file ErlNifBinary data; if (!enif_alloc_binary_compat(env, sinfo.st_size, &data)) { return enif_make_tuple2(env, ATOM_ERROR, ATOM_ALLOCATION_ERROR); } // Read the whole file into our binary if (pread(handle->fd, data.data, data.size, 0) == -1) { return errno_error_tuple(env, ATOM_PREAD_ERROR, errno); } return enif_make_tuple2(env, ATOM_OK, enif_make_binary(env, &data)); } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_lock_writedata(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_lock_handle* handle; ErlNifBinary data; if (enif_get_resource(env, argv[0], bitcask_lock_RESOURCE, (void**)&handle) && enif_inspect_binary(env, argv[1], &data)) { if (handle->is_write_lock) { // Truncate the file first, to ensure that the lock file only contains what // we're about to write if (ftruncate(handle->fd, 0) == -1) { return errno_error_tuple(env, ATOM_FTRUNCATE_ERROR, errno); } // Write the new blob of data to the lock file. Note that we use O_SYNC to // ensure that the data is available ASAP to reading processes. if (pwrite(handle->fd, data.data, data.size, 0) == -1) { return errno_error_tuple(env, ATOM_PWRITE_ERROR, errno); } return ATOM_OK; } else { // Tried to write data to a read lock return enif_make_tuple2(env, ATOM_ERROR, ATOM_LOCK_NOT_WRITABLE); } } else { return enif_make_badarg(env); } } int get_file_open_flags(ErlNifEnv* env, ERL_NIF_TERM list) { int flags = O_RDWR | O_APPEND | O_CREAT; ERL_NIF_TERM head, tail; while (enif_get_list_cell(env, list, &head, &tail)) { if (head == ATOM_CREATE) { flags = O_CREAT | O_EXCL | O_RDWR | O_APPEND; } else if (head == ATOM_READONLY) { flags = O_RDONLY; } else if (head == ATOM_O_SYNC) { flags |= O_SYNC; } list = tail; } return flags; } ERL_NIF_TERM bitcask_nifs_file_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { char filename[4096]; if (enif_get_string(env, argv[0], filename, sizeof(filename), ERL_NIF_LATIN1) && enif_is_list(env, argv[1])) { int flags = get_file_open_flags(env, argv[1]); int fd = open(filename, flags, S_IREAD | S_IWRITE); if (fd > -1) { // Setup a resource for our handle bitcask_file_handle* handle = enif_alloc_resource_compat(env, bitcask_file_RESOURCE, sizeof(bitcask_file_handle)); memset(handle, '\0', sizeof(bitcask_file_handle)); handle->fd = fd; ERL_NIF_TERM result = enif_make_resource(env, handle); enif_release_resource_compat(env, handle); return enif_make_tuple2(env, ATOM_OK, result); } else { return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle)) { if (handle->fd > 0) { /* TODO: Check for EIO */ close(handle->fd); handle->fd = -1; } return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_sync(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle)) { int rc = fsync(handle->fd); if (rc != -1) { return ATOM_OK; } else { return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_pread(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; unsigned long offset_ul; unsigned long count_ul; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle) && enif_get_ulong(env, argv[1], &offset_ul) && /* Offset */ enif_get_ulong(env, argv[2], &count_ul)) /* Count */ { ErlNifBinary bin; off_t offset = offset_ul; size_t count = count_ul; if (!enif_alloc_binary(count, &bin)) { return enif_make_tuple2(env, ATOM_ERROR, ATOM_ALLOCATION_ERROR); } ssize_t bytes_read = pread(handle->fd, bin.data, count, offset); if (bytes_read == count) { /* Good read; return {ok, Bin} */ return enif_make_tuple2(env, ATOM_OK, enif_make_binary(env, &bin)); } else if (bytes_read > 0) { /* Partial read; need to resize our binary (bleh) and return {ok, Bin} */ if (enif_realloc_binary(&bin, bytes_read)) { return enif_make_tuple2(env, ATOM_OK, enif_make_binary(env, &bin)); } else { /* Realloc failed; cleanup and bail */ enif_release_binary(&bin); return enif_make_tuple2(env, ATOM_ERROR, ATOM_ALLOCATION_ERROR); } } else if (bytes_read == 0) { /* EOF */ enif_release_binary(&bin); return ATOM_EOF; } else { /* Read failed altogether */ enif_release_binary(&bin); return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_pwrite(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; unsigned long offset_ul; ErlNifBinary bin; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle) && enif_get_ulong(env, argv[1], &offset_ul) && /* Offset */ enif_inspect_iolist_as_binary(env, argv[2], &bin)) /* Bytes to write */ { unsigned char* buf = bin.data; ssize_t bytes_written = 0; ssize_t count = bin.size; off_t offset = offset_ul; while (count > 0) { bytes_written = pwrite(handle->fd, buf, count, offset); if (bytes_written > 0) { count -= bytes_written; offset += bytes_written; buf += bytes_written; } else { /* Write failed altogether */ return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } /* Write done */ return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_read(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; size_t count; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle) && enif_get_ulong(env, argv[1], &count)) /* Count */ { ErlNifBinary bin; if (!enif_alloc_binary(count, &bin)) { return enif_make_tuple2(env, ATOM_ERROR, ATOM_ALLOCATION_ERROR); } ssize_t bytes_read = read(handle->fd, bin.data, count); if (bytes_read == count) { /* Good read; return {ok, Bin} */ return enif_make_tuple2(env, ATOM_OK, enif_make_binary(env, &bin)); } else if (bytes_read > 0) { /* Partial read; need to resize our binary (bleh) and return {ok, Bin} */ if (enif_realloc_binary(&bin, bytes_read)) { return enif_make_tuple2(env, ATOM_OK, enif_make_binary(env, &bin)); } else { /* Realloc failed; cleanup and bail */ enif_release_binary(&bin); return enif_make_tuple2(env, ATOM_ERROR, ATOM_ALLOCATION_ERROR); } } else if (bytes_read == 0) { /* EOF */ enif_release_binary(&bin); return ATOM_EOF; } else { /* Read failed altogether */ enif_release_binary(&bin); return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_write(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; ErlNifBinary bin; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle) && enif_inspect_iolist_as_binary(env, argv[1], &bin)) /* Bytes to write */ { unsigned char* buf = bin.data; ssize_t bytes_written = 0; ssize_t count = bin.size; while (count > 0) { bytes_written = write(handle->fd, buf, count); if (bytes_written > 0) { count -= bytes_written; buf += bytes_written; } else { /* Write failed altogether */ return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } /* Write done */ return ATOM_OK; } else { return enif_make_badarg(env); } } // Returns 0 if failed to parse lseek style argument for file_position static int parse_seek_offset(ErlNifEnv* env, ERL_NIF_TERM arg, off_t * ofs, int * whence) { long long_ofs; int arity; const ERL_NIF_TERM* tuple_elements; if (enif_get_long(env, arg, &long_ofs)) { *whence = SEEK_SET; *ofs = (off_t)long_ofs; return 1; } else if (enif_get_tuple(env, arg, &arity, &tuple_elements) && arity == 2 && enif_get_long(env, tuple_elements[1], &long_ofs)) { *ofs = (off_t)long_ofs; if (tuple_elements[0] == ATOM_CUR) { *whence = SEEK_CUR; } else if (tuple_elements[0] == ATOM_BOF) { *whence = SEEK_SET; } else if (tuple_elements[0] == ATOM_EOF) { *whence = SEEK_END; } else { return 0; } return 1; } else { return 0; } } ERL_NIF_TERM bitcask_nifs_file_position(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; off_t offset; int whence; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle) && parse_seek_offset(env, argv[1], &offset, &whence)) { off_t new_offset = lseek(handle->fd, offset, whence); if (new_offset != -1) { return enif_make_tuple2(env, ATOM_OK, enif_make_ulong(env, new_offset)); } else { /* Write failed altogether */ return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_seekbof(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle)) { if (lseek(handle->fd, 0, SEEK_SET) != (off_t)-1) { return ATOM_OK; } else { /* Write failed altogether */ return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } } else { return enif_make_badarg(env); } } ERL_NIF_TERM bitcask_nifs_file_truncate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { bitcask_file_handle* handle; if (enif_get_resource(env, argv[0], bitcask_file_RESOURCE, (void**)&handle)) { off_t ofs = lseek(handle->fd, 0, SEEK_CUR); if (ofs == (off_t)-1) { return enif_make_tuple2(env, ATOM_ERROR, errno_atom(env, errno)); } if (ftruncate(handle->fd, ofs) == -1) { return errno_error_tuple(env, ATOM_FTRUNCATE_ERROR, errno); } return ATOM_OK; } else { return enif_make_badarg(env); } } ERL_NIF_TERM errno_atom(ErlNifEnv* env, int error) { return enif_make_atom(env, erl_errno_id(error)); } ERL_NIF_TERM errno_error_tuple(ErlNifEnv* env, ERL_NIF_TERM key, int error) { // Construct a tuple of form: {error, {Key, ErrnoAtom}} return enif_make_tuple2(env, ATOM_ERROR, enif_make_tuple2(env, key, errno_atom(env, error))); } // Send messages to all processes that want to be awoken next time // pending is merged. static void msg_pending_awaken(ErlNifEnv* env, bitcask_keydir* keydir, ERL_NIF_TERM msg) { ErlNifEnv* msg_env = enif_alloc_env(); int idx; for (idx = 0; idx < keydir->pending_awaken_count; idx++) { enif_clear_env(msg_env); enif_send(env, &keydir->pending_awaken[idx], msg_env, msg); } enif_free_env(msg_env); } // Merge pending hash into entries hash and awaken any pids that want to // start iterating once we are merged. keydir must be locked before calling. static void merge_pending_entries(ErlNifEnv* env, bitcask_keydir* keydir) { DEBUG("Merge pending entries. Keydir before merging\r\n"); DEBUG_KEYDIR(keydir); khiter_t pend_itr; for (pend_itr = kh_begin(keydir->pending); pend_itr != kh_end(keydir->pending); ++pend_itr) { if (kh_exist(keydir->pending, pend_itr)) { bitcask_keydir_entry* pending_entry = kh_key(keydir->pending, pend_itr); khiter_t ent_itr = kh_get(entries, keydir->entries, pending_entry); DEBUG("Pending Entry: key=%s key_sz=%d file_id=%d tstamp=%u offset=%u size=%d\r\n", pending_entry->key, pending_entry->key_sz, pending_entry->file_id, (unsigned int) pending_entry->tstamp, (unsigned int) pending_entry->offset, pending_entry->total_sz); if (ent_itr == kh_end(keydir->entries)) { /* entries: empty, pending:tombstone */ if (is_pending_tombstone(pending_entry)) { /* nop - stats were not updated when tombstone written for ** empty entry */ free(pending_entry); } /* entries: empty, pending:value */ else { // Move to entries, do not free kh_put_set(entries, keydir->entries, pending_entry); } } else { bitcask_keydir_entry* entries_entry = kh_key(keydir->entries, ent_itr); DEBUG("Entries Entry: key=%s key_sz=%d file_id=%d statmp=%u offset=%u size=%d\r\n", entries_entry->key, entries_entry->key_sz, entries_entry->file_id, (unsigned int) entries_entry->tstamp, (unsigned int) entries_entry->offset, entries_entry->total_sz); /* entries: present, pending:tombstone */ if (is_pending_tombstone(pending_entry)) { remove_entry(keydir, ent_itr); free(pending_entry); } /* entries: present, pending:value */ else { free_entry(entries_entry); kh_key(keydir->entries, ent_itr) = pending_entry; } } } } // Wake up all sleeping pids msg_pending_awaken(env, keydir, ATOM_READY); // Free all resources for keydir folding kh_destroy(entries, keydir->pending); DEBUG2("LINE %d keydir->pending = NULL\r\n", __LINE__); keydir->pending = NULL; keydir->pending_updated = 0; keydir->pending_start_time = 0; keydir->pending_start_epoch = 0; if (keydir->pending_awaken != NULL) { free(keydir->pending_awaken); } keydir->pending_awaken = NULL; keydir->pending_awaken_count = 0; keydir->pending_awaken_size = 0; DEBUG("Merge pending entries completed\r\n"); DEBUG_KEYDIR(keydir); } static void lock_release(bitcask_lock_handle* handle) { if (handle->fd > 0) { // If this is a write lock, we need to delete the file as part of cleanup. But be // sure to do this BEFORE letting go of the file handle so as to ensure consistency // with other readers. if (handle->is_write_lock) { // TODO: Come up with some way to complain/error log if this unlink failed for some // reason!! unlink(handle->filename); } close(handle->fd); handle->fd = -1; } } static void free_keydir(bitcask_keydir* keydir) { // Delete all the entries in the hash table, which also has the effect of // freeing up all resources associated with the table. khiter_t itr; bitcask_keydir_entry* current_entry; for (itr = kh_begin(keydir->entries); itr != kh_end(keydir->entries); ++itr) { if (kh_exist(keydir->entries, itr)) { current_entry = kh_key(keydir->entries, itr); free_entry(current_entry); } } kh_destroy(entries, keydir->entries); bitcask_fstats_entry* curr_f; for (itr = kh_begin(keydir->fstats); itr != kh_end(keydir->fstats); ++itr) { if (kh_exist(keydir->fstats, itr)) { curr_f = kh_val(keydir->fstats, itr); free(curr_f); } } kh_destroy(fstats, keydir->fstats); free(keydir); } static void bitcask_nifs_keydir_resource_cleanup(ErlNifEnv* env, void* arg) { bitcask_keydir_handle* handle = (bitcask_keydir_handle*)arg; bitcask_keydir* keydir = handle->keydir; // First, check that there is even a keydir available. If keydir_release // was invoked manually, we might have already cleaned up the keydir // and this round of cleanup can noop. Otherwise, clear out the handle's // reference to the keydir so that repeat calls function as expected if (!handle->keydir) { return; } else { if (handle->iterating) { LOCK(handle->keydir); itr_release_internal(env, handle); UNLOCK(handle->keydir); } handle->keydir = 0; } // If the keydir has a lock, we need to decrement the refcount and // potentially release it if (keydir->mutex) { bitcask_priv_data* priv = (bitcask_priv_data*)enif_priv_data(env); enif_mutex_lock(priv->global_keydirs_lock); // Remember biggest_file_id in case someone re-opens the same name uint32_t global_biggest = 0, the_biggest = 0; khiter_t itr_biggest_file_id = kh_get(global_biggest_file_id, priv->global_biggest_file_id, keydir->name); if (itr_biggest_file_id != kh_end(priv->global_biggest_file_id)) { global_biggest = kh_val(priv->global_biggest_file_id, itr_biggest_file_id); } the_biggest = (global_biggest > keydir->biggest_file_id) ? \ global_biggest : keydir->biggest_file_id; the_biggest++; kh_put2(global_biggest_file_id, priv->global_biggest_file_id, strdup(keydir->name), the_biggest); keydir->refcount--; if (keydir->refcount == 0) { // This is the last reference to the named keydir. As such, // remove it from the hashtable so no one else tries to use it khiter_t itr = kh_get(global_keydirs, priv->global_keydirs, keydir->name); kh_del(global_keydirs, priv->global_keydirs, itr); } else { // At least one other reference; just throw away our keydir pointer // so the check below doesn't release the memory. keydir = 0; } // Unlock ASAP. Wanted to avoid holding this mutex while we clean up the // keydir, since it may take a while to walk a large keydir and free each // entry. enif_mutex_unlock(priv->global_keydirs_lock); } // If keydir is still defined, it's either privately owned or has a // refcount of 0. Either way, we want to release it. if (keydir) { if (keydir->mutex) { enif_mutex_destroy(keydir->mutex); } free_keydir(keydir); } } static void bitcask_nifs_lock_resource_cleanup(ErlNifEnv* env, void* arg) { bitcask_lock_handle* handle = (bitcask_lock_handle*)arg; lock_release(handle); } static void bitcask_nifs_file_resource_cleanup(ErlNifEnv* env, void* arg) { bitcask_file_handle* handle = (bitcask_file_handle*)arg; if (handle->fd > -1) { close(handle->fd); } } #ifdef BITCASK_DEBUG void dump_fstats(bitcask_keydir* keydir) { bitcask_fstats_entry* curr_f; khiter_t itr; for (itr = kh_begin(keydir->fstats); itr != kh_end(keydir->fstats); ++itr) { if (kh_exist(keydir->fstats, itr)) { curr_f = kh_val(keydir->fstats, itr); DEBUG("fstats %d live=(%d,%d) total=(%d,%d)\r\n", (int) curr_f->file_id, (int) curr_f->live_keys, (int) curr_f->live_bytes, (int) curr_f->total_keys, (int) curr_f->total_bytes); } } } #endif static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { bitcask_keydir_RESOURCE = enif_open_resource_type_compat(env, "bitcask_keydir_resource", &bitcask_nifs_keydir_resource_cleanup, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 0); bitcask_lock_RESOURCE = enif_open_resource_type_compat(env, "bitcask_lock_resource", &bitcask_nifs_lock_resource_cleanup, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 0); bitcask_file_RESOURCE = enif_open_resource_type_compat(env, "bitcask_file_resource", &bitcask_nifs_file_resource_cleanup, ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 0); // Initialize shared keydir hashtable bitcask_priv_data* priv = malloc(sizeof(bitcask_priv_data)); priv->global_biggest_file_id = kh_init(global_biggest_file_id); priv->global_keydirs = kh_init(global_keydirs); priv->global_keydirs_lock = enif_mutex_create("bitcask_global_handles_lock"); *priv_data = priv; // Initialize atoms that we use throughout the NIF. ATOM_ALLOCATION_ERROR = enif_make_atom(env, "allocation_error"); ATOM_ALREADY_EXISTS = enif_make_atom(env, "already_exists"); ATOM_BITCASK_ENTRY = enif_make_atom(env, "bitcask_entry"); ATOM_ERROR = enif_make_atom(env, "error"); ATOM_FALSE = enif_make_atom(env, "false"); ATOM_FSTAT_ERROR = enif_make_atom(env, "fstat_error"); ATOM_FTRUNCATE_ERROR = enif_make_atom(env, "ftruncate_error"); ATOM_GETFL_ERROR = enif_make_atom(env, "getfl_error"); ATOM_ILT_CREATE_ERROR = enif_make_atom(env, "ilt_create_error"); ATOM_ITERATION_IN_PROCESS = enif_make_atom(env, "iteration_in_process"); ATOM_ITERATION_NOT_PERMITTED = enif_make_atom(env, "iteration_not_permitted"); ATOM_ITERATION_NOT_STARTED = enif_make_atom(env, "iteration_not_started"); ATOM_LOCK_NOT_WRITABLE = enif_make_atom(env, "lock_not_writable"); ATOM_NOT_FOUND = enif_make_atom(env, "not_found"); ATOM_NOT_READY = enif_make_atom(env, "not_ready"); ATOM_OK = enif_make_atom(env, "ok"); ATOM_OUT_OF_DATE = enif_make_atom(env, "out_of_date"); ATOM_PREAD_ERROR = enif_make_atom(env, "pread_error"); ATOM_PWRITE_ERROR = enif_make_atom(env, "pwrite_error"); ATOM_READY = enif_make_atom(env, "ready"); ATOM_SETFL_ERROR = enif_make_atom(env, "setfl_error"); ATOM_TRUE = enif_make_atom(env, "true"); ATOM_UNDEFINED = enif_make_atom(env, "undefined"); ATOM_EOF = enif_make_atom(env, "eof"); ATOM_CREATE = enif_make_atom(env, "create"); ATOM_READONLY = enif_make_atom(env, "readonly"); ATOM_O_SYNC = enif_make_atom(env, "o_sync"); ATOM_CUR = enif_make_atom(env, "cur"); ATOM_BOF = enif_make_atom(env, "bof"); return 0; } ERL_NIF_INIT(bitcask_nifs, nif_funcs, &on_load, NULL, NULL, NULL); #pragma GCC diagnostic pop bitcask-2.1.0/c_src/khash.h0000644000232200023220000004441313655023466016060 0ustar debalancedebalance/* The MIT License Copyright (c) 2008, 2009 by attractor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* An example: #include "khash.h" KHASH_MAP_INIT_INT(32, char) int main() { int ret, is_missing; khiter_t k; khash_t(32) *h = kh_init(32); k = kh_put(32, h, 5, &ret); if (!ret) kh_del(32, h, k); kh_value(h, k) = 10; k = kh_get(32, h, 10); is_missing = (k == kh_end(h)); k = kh_get(32, h, 5); kh_del(32, h, k); for (k = kh_begin(h); k != kh_end(h); ++k) if (kh_exist(h, k)) kh_value(h, k) = 1; kh_destroy(32, h); return 0; } */ /* 2009-09-26 (0.2.4): * Improve portability 2008-09-19 (0.2.3): * Corrected the example * Improved interfaces 2008-09-11 (0.2.2): * Improved speed a little in kh_put() 2008-09-10 (0.2.1): * Added kh_clear() * Fixed a compiling error 2008-09-02 (0.2.0): * Changed to token concatenation which increases flexibility. 2008-08-31 (0.1.2): * Fixed a bug in kh_get(), which has not been tested previously. 2008-08-31 (0.1.1): * Added destructor */ #ifndef __AC_KHASH_H #define __AC_KHASH_H /*! @header Generic hash table library. @copyright Heng Li */ #define AC_VERSION_KHASH_H "0.2.4" #include #include #include /* compipler specific configuration */ #if UINT_MAX == 0xffffffffu typedef unsigned int khint32_t; #elif ULONG_MAX == 0xffffffffu typedef unsigned long khint32_t; #endif #if ULONG_MAX == ULLONG_MAX typedef unsigned long khint64_t; #else typedef unsigned long long khint64_t; #endif #ifdef _MSC_VER #define inline __inline #endif typedef khint32_t khint_t; typedef khint_t khiter_t; #define __ac_HASH_PRIME_SIZE 32 static const khint32_t __ac_prime_list[__ac_HASH_PRIME_SIZE] = { 0ul, 3ul, 11ul, 23ul, 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul }; #define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) #define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) #define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) #define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) #define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) #define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) #define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) static const double __ac_HASH_UPPER = 0.77; #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ typedef struct { \ khint_t n_buckets, size, n_occupied, upper_bound; \ khint32_t *flags; \ khkey_t *keys; \ khval_t *vals; \ } kh_##name##_t; \ static inline kh_##name##_t *kh_init_##name() { \ return (kh_##name##_t*)calloc(1, sizeof(kh_##name##_t)); \ } \ static inline void kh_destroy_##name(kh_##name##_t *h) \ { \ if (h) { \ free(h->keys); free(h->flags); \ free(h->vals); \ free(h); \ } \ } \ static inline void kh_clear_##name(kh_##name##_t *h) \ { \ if (h && h->flags) { \ memset(h->flags, 0xaa, ((h->n_buckets>>4) + 1) * sizeof(khint32_t)); \ h->size = h->n_occupied = 0; \ } \ } \ static inline khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ { \ if (h->n_buckets) { \ khint_t inc, k, i, last; \ k = __hash_func(key); i = k % h->n_buckets; \ inc = 1 + k % (h->n_buckets - 1); last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ if (i + inc >= h->n_buckets) i = i + inc - h->n_buckets; \ else i += inc; \ if (i == last) return h->n_buckets; \ } \ return __ac_iseither(h->flags, i)? h->n_buckets : i; \ } else return 0; \ } \ static inline khint_t kh_get_custom_##name(const kh_##name##_t *h, void* key, khint_t (*hash_func)(void*), khint_t (*eq_func)(khkey_t, void*)) \ { \ if (h->n_buckets) { \ khint_t inc, k, i, last; \ k = hash_func(key); i = k % h->n_buckets; \ inc = 1 + k % (h->n_buckets - 1); last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !eq_func(h->keys[i], key))) { \ if (i + inc >= h->n_buckets) i = i + inc - h->n_buckets; \ else i += inc; \ if (i == last) return h->n_buckets; \ } \ return __ac_iseither(h->flags, i)? h->n_buckets : i; \ } else return 0; \ } \ static inline void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ { \ khint32_t *new_flags = 0; \ khint_t j = 1; \ { \ khint_t t = __ac_HASH_PRIME_SIZE - 1; \ while (__ac_prime_list[t] > new_n_buckets) --t; \ new_n_buckets = __ac_prime_list[t+1]; \ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; \ else { \ new_flags = (khint32_t*)malloc(((new_n_buckets>>4) + 1) * sizeof(khint32_t)); \ memset(new_flags, 0xaa, ((new_n_buckets>>4) + 1) * sizeof(khint32_t)); \ if (h->n_buckets < new_n_buckets) { \ h->keys = (khkey_t*)realloc(h->keys, new_n_buckets * sizeof(khkey_t)); \ if (kh_is_map) \ h->vals = (khval_t*)realloc(h->vals, new_n_buckets * sizeof(khval_t)); \ } \ } \ } \ if (j) { \ for (j = 0; j != h->n_buckets; ++j) { \ if (__ac_iseither(h->flags, j) == 0) { \ khkey_t key = h->keys[j]; \ khval_t val; \ if (kh_is_map) val = h->vals[j]; \ __ac_set_isdel_true(h->flags, j); \ while (1) { \ khint_t inc, k, i; \ k = __hash_func(key); \ i = k % new_n_buckets; \ inc = 1 + k % (new_n_buckets - 1); \ while (!__ac_isempty(new_flags, i)) { \ if (i + inc >= new_n_buckets) i = i + inc - new_n_buckets; \ else i += inc; \ } \ __ac_set_isempty_false(new_flags, i); \ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { \ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ __ac_set_isdel_true(h->flags, i); \ } else { \ h->keys[i] = key; \ if (kh_is_map) h->vals[i] = val; \ break; \ } \ } \ } \ } \ if (h->n_buckets > new_n_buckets) { \ h->keys = (khkey_t*)realloc(h->keys, new_n_buckets * sizeof(khkey_t)); \ if (kh_is_map) \ h->vals = (khval_t*)realloc(h->vals, new_n_buckets * sizeof(khval_t)); \ } \ free(h->flags); \ h->flags = new_flags; \ h->n_buckets = new_n_buckets; \ h->n_occupied = h->size; \ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ } \ } \ static inline khint_t kh_put_will_resize_##name(kh_##name##_t *h) \ { \ if (h->n_occupied >= h->upper_bound) { \ return 1; \ } else { \ return 0; \ } \ } \ static inline khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ { \ khint_t x; \ if (h->n_occupied >= h->upper_bound) { \ if (h->n_buckets > (h->size<<1)) kh_resize_##name(h, h->n_buckets - 1); \ else kh_resize_##name(h, h->n_buckets + 1); \ } \ { \ khint_t inc, k, i, site, last; \ x = site = h->n_buckets; k = __hash_func(key); i = k % h->n_buckets; \ if (__ac_isempty(h->flags, i)) x = i; \ else { \ inc = 1 + k % (h->n_buckets - 1); last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ if (__ac_isdel(h->flags, i)) site = i; \ if (i + inc >= h->n_buckets) i = i + inc - h->n_buckets; \ else i += inc; \ if (i == last) { x = site; break; } \ } \ if (x == h->n_buckets) { \ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ else x = i; \ } \ } \ } \ if (__ac_isempty(h->flags, x)) { \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; ++h->n_occupied; \ *ret = 1; \ } else if (__ac_isdel(h->flags, x)) { \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; \ *ret = 2; \ } else *ret = 0; \ return x; \ } \ static inline void kh_del_##name(kh_##name##_t *h, khint_t x) \ { \ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ __ac_set_isdel_true(h->flags, x); \ --h->size; \ } \ } /* --- BEGIN OF HASH FUNCTIONS --- */ /*! @function @abstract Integer hash function @param key The integer [khint32_t] @return The hash value [khint_t] */ #define kh_int_hash_func(key) (khint32_t)(key) /*! @function @abstract Integer comparison function */ #define kh_int_hash_equal(a, b) ((a) == (b)) /*! @function @abstract 64-bit integer hash function @param key The integer [khint64_t] @return The hash value [khint_t] */ #define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) /*! @function @abstract 64-bit integer comparison function */ #define kh_int64_hash_equal(a, b) ((a) == (b)) /*! @function @abstract const char* hash function @param s Pointer to a null terminated string @return The hash value */ static inline khint_t __ac_X31_hash_string(const char *s) { khint_t h = *s; if (h) for (++s ; *s; ++s) h = (h << 5) - h + *s; return h; } /*! @function @abstract Another interface to const char* hash function @param key Pointer to a null terminated string [const char*] @return The hash value [khint_t] */ #define kh_str_hash_func(key) __ac_X31_hash_string(key) /*! @function @abstract Const char* comparison function */ #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) /* --- END OF HASH FUNCTIONS --- */ /* Other necessary macros... */ /*! @abstract Type of the hash table. @param name Name of the hash table [symbol] */ #define khash_t(name) kh_##name##_t /*! @function @abstract Initiate a hash table. @param name Name of the hash table [symbol] @return Pointer to the hash table [khash_t(name)*] */ #define kh_init(name) kh_init_##name() /*! @function @abstract Destroy a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_destroy(name, h) kh_destroy_##name(h) /*! @function @abstract Reset a hash table without deallocating memory. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_clear(name, h) kh_clear_##name(h) /*! @function @abstract Resize a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param s New size [khint_t] */ #define kh_resize(name, h, s) kh_resize_##name(h, s) /*! @function @abstract Insert a key to the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @param r Extra return code: 0 if the key is present in the hash table; 1 if the bucket is empty (never used); 2 if the element in the bucket has been deleted [int*] @return Iterator to the inserted element [khint_t] */ #define kh_put_will_resize(name, h) kh_put_will_resize_##name(h) #define kh_put(name, h, k, r) kh_put_##name(h, k, r) /*! @function @abstract Retrieve a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @return Iterator to the found element, or kh_end(h) is the element is absent [khint_t] */ #define kh_get(name, h, k) kh_get_##name(h, k) /*! @function @abstract Retrieve a key from the hash table using custom hash and equals functions. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key @param hf Hash function @param ef Equals (key, custom key) function @return Iterator to the found element, or kh_end(h) is the element is absent [khint_t] */ #define kh_get_custom(name, h, k, hf, ef) kh_get_custom_##name(h, k, hf, ef) /*! @function @abstract Remove a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Iterator to the element to be deleted [khint_t] */ #define kh_del(name, h, k) kh_del_##name(h, k) /*! @function @abstract Test whether a bucket contains data. @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return 1 if containing data; 0 otherwise [int] */ #define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) /*! @function @abstract Get key given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Key [type of keys] */ #define kh_key(h, x) ((h)->keys[x]) /*! @function @abstract Get value given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Value [type of values] @discussion For hash sets, calling this results in segfault. */ #define kh_val(h, x) ((h)->vals[x]) /*! @function @abstract Alias of kh_val() */ #define kh_value(h, x) ((h)->vals[x]) /*! @function @abstract Get the start iterator @param h Pointer to the hash table [khash_t(name)*] @return The start iterator [khint_t] */ #define kh_begin(h) (khint_t)(0) /*! @function @abstract Get the end iterator @param h Pointer to the hash table [khash_t(name)*] @return The end iterator [khint_t] */ #define kh_end(h) ((h)->n_buckets) /*! @function @abstract Get the number of elements in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of elements in the hash table [khint_t] */ #define kh_size(h) ((h)->size) /*! @function @abstract Get the number of buckets in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of buckets in the hash table [khint_t] */ #define kh_n_buckets(h) ((h)->n_buckets) /* More conenient interfaces */ /*! @function @abstract Instantiate a hash set containing integer keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_INT(name) \ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) /*! @function @abstract Instantiate a hash map containing integer keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_INT(name, khval_t) \ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) /*! @function @abstract Instantiate a hash map containing 64-bit integer keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_INT64(name) \ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) /*! @function @abstract Instantiate a hash map containing 64-bit integer keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_INT64(name, khval_t) \ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) typedef const char *kh_cstr_t; /*! @function @abstract Instantiate a hash map containing const char* keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_STR(name) \ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) /*! @function @abstract Instantiate a hash map containing const char* keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_STR(name, khval_t) \ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) #endif /* __AC_KHASH_H */ bitcask-2.1.0/c_src/murmurhash.c0000644000232200023220000000723713655023466017153 0ustar debalancedebalance/* Murmurhash from http://sites.google.com/site/murmurhash/ All code is released to the public domain. For business purposes, Murmurhash is under the MIT license. */ #include "murmurhash.h" #if defined(__x86_64__) // ------------------------------------------------------------------- // // The same caveats as 32-bit MurmurHash2 apply here - beware of alignment // and endian-ness issues if used across multiple platforms. // // 64-bit hash for 64-bit platforms uint64_t MurmurHash64A ( const void * key, int len, unsigned int seed ) { const uint64_t m = 0xc6a4a7935bd1e995; const int r = 47; uint64_t h = seed ^ (len * m); const uint64_t * data = (const uint64_t *)key; const uint64_t * end = data + (len/8); while(data != end) { uint64_t k = *data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char * data2 = (const unsigned char*)data; switch(len & 7) { case 7: h ^= ((uint64_t)data2[6]) << 48; case 6: h ^= ((uint64_t)data2[5]) << 40; case 5: h ^= ((uint64_t)data2[4]) << 32; case 4: h ^= ((uint64_t)data2[3]) << 24; case 3: h ^= ((uint64_t)data2[2]) << 16; case 2: h ^= ((uint64_t)data2[1]) << 8; case 1: h ^= ((uint64_t)data2[0]); h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } #elif defined(__i386__) // ------------------------------------------------------------------- // // Note - This code makes a few assumptions about how your machine behaves - // // 1. We can read a 4-byte value from any address without crashing // 2. sizeof(int) == 4 // // And it has a few limitations - // // 1. It will not work incrementally. // 2. It will not produce the same results on little-endian and big-endian // machines. unsigned int MurmurHash2 ( const void * key, int len, unsigned int seed ) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const unsigned int m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value unsigned int h = seed ^ len; // Mix 4 bytes at a time into the hash const unsigned char * data = (const unsigned char *)key; while(len >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; // Do a few final mixes of the hash to ensure the last few // bytes are well-incorporated. h ^= h >> 13; h *= m; h ^= h >> 15; return h; } #else // ------------------------------------------------------------------- // // Same as MurmurHash2, but endian- and alignment-neutral. // Half the speed though, alas. unsigned int MurmurHashNeutral2 ( const void * key, int len, unsigned int seed ) { const unsigned int m = 0x5bd1e995; const int r = 24; unsigned int h = seed ^ len; const unsigned char * data = (const unsigned char *)key; while(len >= 4) { unsigned int k; k = data[0]; k |= data[1] << 8; k |= data[2] << 16; k |= data[3] << 24; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; h ^= h >> 13; h *= m; h ^= h >> 15; return h; } #endif bitcask-2.1.0/c_src/erl_nif_util.h0000644000232200023220000000211113655023466017422 0ustar debalancedebalance// ------------------------------------------------------------------- // // bitcask: Eric Brewer-inspired key/value store // // Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. // // This file is provided to you under the Apache License, // Version 2.0 (the "License"); you may not use this file // except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // // ------------------------------------------------------------------- #ifndef ERL_NIF_UTIL_H #define ERL_NIF_UTIL_H #include "erl_nif.h" #include int enif_get_uint64_bin(ErlNifEnv* env, ERL_NIF_TERM term, uint64_t* outvalue); ERL_NIF_TERM enif_make_uint64_bin(ErlNifEnv* env, uint64_t value); #endif bitcask-2.1.0/THANKS0000644000232200023220000000021013655023466014416 0ustar debalancedebalanceThe following people have contributed to Bitcask: Eric Brewer David Smith Justin Sheehy Andy Gross Ryan Tilder Jon Meredith Bob Dionne bitcask-2.1.0/rebar.config.script0000644000232200023220000000333513655023466017303 0ustar debalancedebalancePulseBuild = case os:getenv("BITCASK_PULSE") of false -> false; _ -> true end, case PulseBuild of true -> PulseOpts = [{pulse_no_side_effect,[{erlang,display,1}]}, {pulse_side_effect, [ {bitcask_nifs, '_', '_'} , {bitcask_file, '_', '_'} , {bitcask_time, tstamp, 0} , {prim_file, '_', '_'} , {file, '_', '_'} , {filelib, '_', '_'} , {os, '_', '_'} ]}, {pulse_replace_module, [ {gen_server, pulse_gen_server} , {application, pulse_application} , {supervisor, pulse_supervisor} ]} ], PulseCFlags = [{"CFLAGS", "$CFLAGS -DPULSE"}], UpdConfig = case lists:keysearch(eunit_compile_opts, 1, CONFIG) of {value, {eunit_compile_opts, Opts}} -> lists:keyreplace(eunit_compile_opts, 1, CONFIG, {eunit_compile_opts, Opts ++ PulseOpts}); _ -> [{eunit_compile_opts, PulseOpts} | CONFIG] end, case lists:keysearch(port_env, 1, UpdConfig) of {value, {port_env, PortEnv}} -> lists:keyreplace(port_env, 1, UpdConfig, {port_env, PortEnv ++ PulseCFlags}); _ -> [{port_env, PulseCFlags} | UpdConfig] end; false -> CONFIG end. bitcask-2.1.0/src/0000755000232200023220000000000013655023466014301 5ustar debalancedebalancebitcask-2.1.0/src/bitcask_time.erl0000644000232200023220000000442213655023466017445 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2011 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_time). -export([tstamp/0]). -export([test__set_fudge/1, test__get_fudge/0, test__incr_fudge/1, test__clear_fudge/0, test__time_travel_loop_sleep/0]). -define(KEY, bitcask_time_fudge). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -endif. %% Return number of seconds since 1970 tstamp() -> test__get(?KEY). test__set_fudge(Amount) -> application:set_env(bitcask, ?KEY, Amount). test__get_fudge() -> test__get(?KEY). test__incr_fudge(Amount) -> test__set_fudge(test__get_fudge() + Amount). test__get(Key) -> %% Play games with local process dictionary to avoid looking %% at application controller's ETS table for every call. case get(Key) of undefined -> case application:get_env(bitcask, Key) of undefined -> put(Key, no_testing); _ -> put(Key, yes_testing) end, test__get(Key); no_testing -> {Mega, Sec, _Micro} = os:timestamp(), (Mega * 1000000) + Sec; yes_testing -> {ok, Fudge} = application:get_env(bitcask, Key), Fudge end. test__clear_fudge() -> application:unset_env(bitcask, ?KEY), erase(?KEY). -ifdef(PULSE). test__time_travel_loop_sleep() -> test__incr_fudge(1). -else. test__time_travel_loop_sleep() -> timer:sleep(250). -endif. bitcask-2.1.0/src/bitcask_app.erl0000644000232200023220000000227413655023466017272 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_app). -behaviour(application). %% Application callbacks -export([start/2, stop/1]). %% =================================================================== %% Application callbacks %% =================================================================== start(_StartType, _StartArgs) -> bitcask_sup:start_link(). stop(_State) -> ok. bitcask-2.1.0/src/bitcask_nifs.erl0000644000232200023220000007741313655023466017460 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_nifs). -export([init/0, keydir_new/0, keydir_new/1, maybe_keydir_new/1, keydir_mark_ready/1, keydir_put/7, keydir_put/8, keydir_put/9, keydir_put/10, keydir_get/2, keydir_get/3, keydir_get_epoch/1, keydir_remove/2, keydir_remove/5, keydir_copy/1, keydir_fold/5, keydir_itr/3, keydir_itr_next/1, keydir_itr_release/1, keydir_frozen/4, keydir_wait_pending/1, keydir_info/1, keydir_release/1, increment_file_id/1, increment_file_id/2, keydir_trim_fstats/2, update_fstats/8, set_pending_delete/2, lock_acquire/2, lock_release/1, lock_readdata/1, lock_writedata/2, file_open/2, file_close/1, file_sync/1, file_pread/3, file_pwrite/3, file_read/2, file_write/2, file_position/2, file_seekbof/1, file_truncate/1]). -on_load(init/0). -include("bitcask.hrl"). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_skip, [{init,0}]}). -endif. -ifdef(TEST). -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -endif. -compile([export_all, nowarn_export_all]). -include_lib("eunit/include/eunit.hrl"). -endif. -type errno_atom() :: atom(). % POSIX errno as atom -spec init() -> ok | {error, any()}. init() -> case code:priv_dir(bitcask) of {error, bad_name} -> case code:which(?MODULE) of Filename when is_list(Filename) -> SoName = filename:join([filename:dirname(Filename),"../priv", "bitcask"]); _ -> SoName = filename:join("../priv", "bitcask") end; Dir -> SoName = filename:join(Dir, "bitcask") end, erlang:load_nif(SoName, 0). %% =================================================================== %% Internal functions %% =================================================================== %% %% Most of the functions below are actually defined in c_src/bitcask_nifs.c %% See that file for the real functionality of the bitcask_nifs module. %% The definitions here are only to satisfy trivial static analysis. %% -spec keydir_new() -> {ok, reference()}. keydir_new() -> erlang:nif_error({error, not_loaded}). -spec keydir_new(string()) -> {ok, reference()} | {ready, reference()} | {not_ready, reference()} | {error, not_ready}. keydir_new(Name) when is_list(Name) -> erlang:nif_error({error, not_loaded}). -spec maybe_keydir_new(string()) -> {ready, reference()} | {error, not_ready}. maybe_keydir_new(Name) when is_list(Name) -> erlang:nif_error({error, not_loaded}). -spec keydir_mark_ready(reference()) -> ok. keydir_mark_ready(_Ref) -> erlang:nif_error({error, not_loaded}). -spec keydir_put(reference(), binary(), integer(), integer(), integer(), integer(), integer()) -> ok | already_exists. keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec) -> keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec, false). -spec keydir_put(reference(), binary(), integer(), integer(), integer(), integer(), integer(), boolean()) -> ok | already_exists. keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec, NewestPutB) -> keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec, NewestPutB, 0, 0). -spec keydir_put(reference(), binary(), integer(), integer(), integer(), integer(), integer(), integer(), integer()) -> ok | already_exists. keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec, OldFileId, OldOffset) -> keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec, false, OldFileId, OldOffset). keydir_put(Ref, Key, FileId, TotalSz, Offset, Tstamp, NowSec, NewestPutB, OldFileId, OldOffset) -> keydir_put_int(Ref, Key, FileId, TotalSz, <>, Tstamp, NowSec, if not NewestPutB -> 0; true -> 1 end, OldFileId, <>). -spec keydir_put_int(reference(), binary(), integer(), integer(), binary(), integer(), 0 | 1, integer(), integer(), binary()) -> ok | already_exists. keydir_put_int(_Ref, _Key, _FileId, _TotalSz, _Offset, _Tstamp, _NowSec, _NewestPutI, _OldFileId, _OldOffset) -> erlang:nif_error({error, not_loaded}). -spec keydir_get(reference(), binary()) -> not_found | #bitcask_entry{}. keydir_get(Ref, Key) -> keydir_get(Ref, Key, 16#ffffffffffffffff). -spec keydir_get(reference(), binary(), integer()) -> not_found | #bitcask_entry{}. keydir_get(Ref, Key, Epoch) -> case keydir_get_int(Ref, Key, Epoch) of E when is_record(E, bitcask_entry) -> <> = E#bitcask_entry.offset, E#bitcask_entry{offset = Offset}; _ -> not_found end. -spec keydir_get_int(reference(), binary(), integer()) -> not_found | #bitcask_entry{}. keydir_get_int(_Ref, _Key, _Epoch) -> erlang:nif_error({error, not_loaded}). keydir_get_epoch(_Ref) -> erlang:nif_error({error, not_loaded}). -spec keydir_remove(reference(), binary()) -> ok | already_exists. keydir_remove(Ref, Key) -> keydir_remove(Ref, Key, bitcask_time:tstamp()). keydir_remove(_Ref, _Key, _TStamp) -> erlang:nif_error({error, not_loaded}). -spec keydir_remove(reference(), binary(), integer(), integer(), integer()) -> ok | already_exists. keydir_remove(Ref, Key, Tstamp, FileId, Offset) -> keydir_remove_int(Ref, Key, Tstamp, FileId, <>, bitcask_time:tstamp()). keydir_remove_int(_Ref, _Key, _Tstamp, _FileId, _Offset, _TStamp) -> erlang:nif_error({error, not_loaded}). -spec keydir_copy(reference()) -> {ok, reference()}. keydir_copy(_Ref) -> erlang:nif_error({error, not_loaded}). -spec keydir_itr(reference(), integer(), integer()) -> ok | out_of_date | {error, iteration_in_process}. keydir_itr(Ref, MaxAge, MaxPuts) -> TS = bitcask_time:tstamp(), keydir_itr_int(Ref, TS, MaxAge, MaxPuts). keydir_itr_int(_Ref, _Ts, _MaxAge, _MaxPuts) -> erlang:nif_error({error, not_loaded}). -spec keydir_itr_next(reference()) -> #bitcask_entry{} | {error, iteration_not_started} | allocation_error | not_found. keydir_itr_next(Ref) -> case keydir_itr_next_int(Ref) of E when is_record(E, bitcask_entry) -> <> = E#bitcask_entry.offset, E#bitcask_entry { offset = Offset }; Other -> Other end. keydir_itr_next_int(_Ref) -> erlang:nif_error({error, not_loaded}). -spec keydir_itr_release(reference()) -> ok. keydir_itr_release(_Ref) -> erlang:nif_error({error, not_loaded}). -spec increment_file_id(reference()) -> {ok, non_neg_integer()}. increment_file_id(_Ref) -> erlang:nif_error({error, not_loaded}). -spec increment_file_id(reference(), non_neg_integer()) -> {ok, non_neg_integer()}. increment_file_id(_Ref, _ConditionalFileId) -> erlang:nif_error({error, not_loaded}). -spec keydir_fold(reference(), fun((any(), any()) -> any()), any(), integer(), integer()) -> any() | {error, any()}. keydir_fold(Ref, Fun, Acc0, MaxAge, MaxPuts) -> FrozenFun = fun() -> keydir_fold_cont(keydir_itr_next(Ref), Ref, Fun, Acc0) end, keydir_frozen(Ref, FrozenFun, MaxAge, MaxPuts). %% Execute the function once the keydir is frozen keydir_frozen(Ref, FrozenFun, MaxAge, MaxPuts) -> case keydir_itr(Ref, MaxAge, MaxPuts) of out_of_date -> case keydir_wait_ready() of ok -> keydir_frozen(Ref, FrozenFun, -1, -1); Else -> Else end; ok -> try FrozenFun() after keydir_itr_release(Ref) end; {error, Reason} -> {error, Reason} end. %% Wait for any pending interation to complete keydir_wait_pending(Ref) -> %% Create an iterator, passing a zero timestamp to force waiting for %% any current iteration to complete case keydir_itr_int(Ref, 0, 0, 0) of out_of_date -> % no iter created, wait for message from last fold_keys receive ready -> ok; error -> {error, shutdown} end; ok -> keydir_itr_release(Ref), ok end. -ifdef(PULSE). keydir_wait_ready() -> keydir_wait_ready(100). keydir_wait_ready(0) -> error({bummer, ?MODULE, "keydir_wait_ready: too deep"}); keydir_wait_ready(N) -> receive ready -> % fold no matter what on second attempt ok; error -> {error, shutdown} after 1000 -> case N =< 99 of true -> erlang:display({?MODULE,?LINE,keydir_wait_ready,retry,N}); false -> ok end, keydir_wait_ready(N-1) end. -else. keydir_wait_ready() -> receive ready -> % fold no matter what on second attempt ok; error -> {error, shutdown} end. -endif. -spec keydir_info(reference()) -> {integer(), integer(), [{integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer()}], {integer(), integer(), boolean(), 'undefined'|integer()}, non_neg_integer()}. keydir_info(_Ref) -> erlang:nif_error({error, not_loaded}). -spec keydir_release(reference()) -> ok. keydir_release(_Ref) -> erlang:nif_error({error, not_loaded}). -spec keydir_trim_fstats(reference(), [integer()]) -> {ok, integer()} | {error, atom()}. keydir_trim_fstats(_Ref, _IDList) -> erlang:nif_error({error, not_loaded}). -spec update_fstats(reference(), non_neg_integer(), non_neg_integer(), integer(), integer(), integer(), integer(), integer() ) -> ok. update_fstats(_Ref, _FileId, _Tstamp, _LiveKeyIncr, _TotalKeyIncr, _LiveIncr, _TotalIncr, _ShouldCreate) -> erlang:nif_error({error, not_loaded}). -spec set_pending_delete(reference(), non_neg_integer()) -> ok. set_pending_delete(_Ref, _FileId) -> erlang:nif_error({error, not_loaded}). -spec lock_acquire(string(), integer()) -> {ok, reference()} | {error, atom()}. lock_acquire(Filename, IsWriteLock) -> bitcask_bump:big(), lock_acquire_int(Filename, IsWriteLock). lock_acquire_int(_Filename, _IsWriteLock) -> erlang:nif_error({error, not_loaded}). -spec lock_release(reference()) -> ok. lock_release(Ref) -> bitcask_bump:big(), lock_release_int(Ref). lock_release_int(_Ref) -> erlang:nif_error({error, not_loaded}). -spec lock_readdata(reference()) -> {ok, binary()} | {fstat_error, integer()} | {error, allocation_error} | {pread_error, integer()}. lock_readdata(Ref) -> bitcask_bump:big(), lock_readdata_int(Ref). lock_readdata_int(_Ref) -> erlang:nif_error({error, not_loaded}). -spec lock_writedata(reference(), binary()) -> ok | {ftruncate_error, errno_atom()} | {pwrite_error, errno_atom()} | {error, lock_not_writable}. lock_writedata(Ref, Data) -> bitcask_bump:big(), lock_writedata_int(Ref, Data). lock_writedata_int(_Ref, _Data) -> erlang:nif_error({error, not_loaded}). file_open(Filename, Opts) -> bitcask_bump:big(), file_open_int(Filename, Opts). file_open_int(_Filename, _Opts) -> erlang:nif_error({error, not_loaded}). file_close(Ref) -> bitcask_bump:big(), file_close_int(Ref). file_close_int(_Ref) -> erlang:nif_error({error, not_loaded}). file_sync(Ref) -> bitcask_bump:big(), file_sync_int(Ref). file_sync_int(_Ref) -> erlang:nif_error({error, not_loaded}). file_pread(Ref, Offset, Size) -> bitcask_bump:big(), file_pread_int(Ref, Offset, Size). file_pread_int(_Ref, _Offset, _Size) -> erlang:nif_error({error, not_loaded}). file_pwrite(Ref, Offset, Bytes) -> bitcask_bump:big(), file_pwrite_int(Ref, Offset, Bytes). file_pwrite_int(_Ref, _Offset, _Bytes) -> erlang:nif_error({error, not_loaded}). file_read(Ref, Size) -> bitcask_bump:big(), file_read_int(Ref, Size). file_read_int(_Ref, _Size) -> erlang:nif_error({error, not_loaded}). file_write(Ref, Bytes) -> bitcask_bump:big(), file_write_int(Ref, Bytes). file_write_int(_Ref, _Bytes) -> erlang:nif_error({error, not_loaded}). file_position(Ref, Position) -> bitcask_bump:big(), file_position_int(Ref, Position). file_position_int(_Ref, _Position) -> erlang:nif_error({error, not_loaded}). file_seekbof(Ref) -> bitcask_bump:big(), file_seekbof_int(Ref). file_seekbof_int(_Ref) -> erlang:nif_error({error, not_loaded}). file_truncate(Ref) -> bitcask_bump:big(), file_truncate_int(Ref). file_truncate_int(_Ref) -> erlang:nif_error({error, not_loaded}). %% =================================================================== %% Internal functions %% =================================================================== keydir_fold_cont(not_found, _Ref, _Fun, Acc0) -> Acc0; keydir_fold_cont(Curr, Ref, Fun, Acc0) -> Acc = Fun(Curr, Acc0), keydir_fold_cont(keydir_itr_next(Ref), Ref, Fun, Acc). %% =================================================================== %% EUnit tests %% =================================================================== -ifdef(TEST). keydir_basic_test_() -> {timeout, 60, fun keydir_basic_test2/0}. keydir_basic_test2() -> {ok, Ref} = keydir_new(), ok = keydir_put(Ref, <<"abc">>, 0, 1234, 0, 1, bitcask_time:tstamp()), {1, 3, [{0, 1, 1, 1234, 1234, 1, 1, _}], {0, 0, false, _},_} = keydir_info(Ref), E = keydir_get(Ref, <<"abc">>), 0 = E#bitcask_entry.file_id, 1234 = E#bitcask_entry.total_sz, 0 = E#bitcask_entry.offset, 1 = E#bitcask_entry.tstamp, already_exists = keydir_put(Ref, <<"abc">>, 0, 1234, 0, 0, bitcask_time:tstamp()), ok = keydir_remove(Ref, <<"abc">>), not_found = keydir_get(Ref, <<"abc">>). keydir_itr_anon_test_() -> {timeout, 60, fun keydir_itr_anon_test2/0}. keydir_itr_anon_test2() -> {ok, Ref} = keydir_new(), keydir_itr_test_base(Ref). keydir_itr_named_test_() -> {timeout, 60, fun keydir_itr_named_test2/0}. keydir_itr_named_test2() -> {not_ready, Ref} = keydir_new("keydir_itr_named_test"), keydir_mark_ready(Ref), keydir_itr_test_base(Ref). keydir_itr_test_base(Ref) -> ok = keydir_put(Ref, <<"abc">>, 0, 1234, 0, 1, bitcask_time:tstamp()), ok = keydir_put(Ref, <<"def">>, 0, 4567, 1234, 2, bitcask_time:tstamp()), ok = keydir_put(Ref, <<"hij">>, 1, 7890, 0, 3, bitcask_time:tstamp()), {3, 9, _, _, _} = keydir_info(Ref), List = keydir_fold(Ref, fun(E, Acc) -> [ E | Acc] end, [], -1, -1), 3 = length(List), true = lists:keymember(<<"abc">>, #bitcask_entry.key, List), true = lists:keymember(<<"def">>, #bitcask_entry.key, List), true = lists:keymember(<<"hij">>, #bitcask_entry.key, List). keydir_copy_test_() -> {timeout, 60, fun keydir_copy_test2/0}. keydir_copy_test2() -> {ok, Ref1} = keydir_new(), ok = keydir_put(Ref1, <<"abc">>, 0, 1234, 0, 1, bitcask_time:tstamp()), ok = keydir_put(Ref1, <<"def">>, 0, 4567, 1234, 2, bitcask_time:tstamp()), ok = keydir_put(Ref1, <<"hij">>, 1, 7890, 0, 3, bitcask_time:tstamp()), {ok, Ref2} = keydir_copy(Ref1), #bitcask_entry { key = <<"abc">>} = keydir_get(Ref2, <<"abc">>). keydir_named_test_() -> {timeout, 60, fun keydir_named_test2/0}. keydir_named_test2() -> {not_ready, Ref} = keydir_new("k1"), ok = keydir_put(Ref, <<"abc">>, 0, 1234, 0, 1, bitcask_time:tstamp()), keydir_mark_ready(Ref), {ready, Ref2} = keydir_new("k1"), #bitcask_entry { key = <<"abc">> } = keydir_get(Ref2, <<"abc">>). keydir_named_not_ready_test_() -> {timeout, 60, fun keydir_named_not_ready_test2/0}. keydir_named_not_ready_test2() -> {not_ready, Ref} = keydir_new("k2"), ok = keydir_put(Ref, <<"abc">>, 0, 1234, 0, 1, bitcask_time:tstamp()), {error, not_ready} = keydir_new("k2"). keydir_itr_while_itr_error_test_() -> {timeout, 60, fun keydir_itr_while_itr_error_test2/0}. keydir_itr_while_itr_error_test2() -> {ok, Ref1} = keydir_new(), ok = keydir_itr(Ref1, -1, -1), try ?assertEqual({error, iteration_in_process}, keydir_itr(Ref1, -1, -1)) after keydir_itr_release(Ref1) end. keydir_double_itr_test_() -> % check iterating flag is cleared {timeout, 60, fun keydir_double_itr_test2/0}. keydir_double_itr_test2() -> {ok, Ref1} = keydir_new(), Folder = fun(_,Acc) -> Acc end, ?assertEqual(acc, keydir_fold(Ref1, Folder, acc, -1, -1)), ?assertEqual(acc, keydir_fold(Ref1, Folder, acc, -1, -1)). keydir_next_notstarted_error_test_() -> {timeout, 60, fun keydir_next_notstarted_error_test2/0}. keydir_next_notstarted_error_test2() -> {ok, Ref1} = keydir_new(), ?assertEqual({error, iteration_not_started}, keydir_itr_next(Ref1)). keydir_del_while_pending_test_() -> {timeout, 60, fun keydir_del_while_pending_test2/0}. keydir_del_while_pending_test2() -> Name = "k_del_while_pending_test", {not_ready, Ref1} = keydir_new(Name), Key = <<"abc">>, T = bitcask_time:tstamp() - 10, ok = keydir_put(Ref1, Key, 0, 1234, 0, T, bitcask_time:tstamp()), keydir_mark_ready(Ref1), ?assertEqual(#bitcask_entry{key = Key, file_id = 0, total_sz = 1234, offset = <<0:64/unsigned-native>>, tstamp = T}, keydir_get_int(Ref1, Key, 16#ffffffffffffffff)), {ready, Ref2} = keydir_new(Name), try %% Start keyfold iterator on Ref2 ok = keydir_itr(Ref2, -1, -1), %% Delete Key ?assertEqual(ok, keydir_remove(Ref1, Key)), ?assertEqual(not_found, keydir_get(Ref1, Key)), %% Keep iterating on Ref2 and check result is [Key] Fun = fun(IterKey, Acc) -> [IterKey | Acc] end, ?assertEqual([#bitcask_entry{key = Key, file_id = 0, total_sz = 1234, offset = 0, tstamp = T}], keydir_fold_cont(keydir_itr_next(Ref2), Ref2, Fun, [])) after %% End iteration ok = keydir_itr_release(Ref2) end, %% Check key is deleted ?assertEqual(not_found, keydir_get(Ref1, Key)). keydir_create_del_while_pending_test_() -> {timeout, 60, fun keydir_create_del_while_pending_test2/0}. keydir_create_del_while_pending_test2() -> Name = "k_create_del_while_pending_test", {not_ready, Ref1} = keydir_new(Name), Key = <<"abc">>, keydir_mark_ready(Ref1), {ready, Ref2} = keydir_new(Name), try %% Start keyfold iterator on Ref2 ok = keydir_itr(Ref2, -1, -1), %% Delete Key ok = keydir_put(Ref1, Key, 0, 1234, 0, 1, bitcask_time:tstamp()), ?assertEqual(#bitcask_entry{key = Key, file_id = 0, total_sz = 1234, offset = <<0:64/unsigned-native>>, tstamp = 1}, keydir_get_int(Ref1, Key, 16#ffffffffffffffff)), ?assertEqual(ok, keydir_remove(Ref1, Key)), ?assertEqual(not_found, keydir_get(Ref1, Key)), %% Keep iterating on Ref2 and check result is [] it was started after iter Fun = fun(IterKey, Acc) -> [IterKey | Acc] end, ?assertEqual([], keydir_fold_cont(keydir_itr_next(Ref2), Ref2, Fun, [])) after %% End iteration ok = keydir_itr_release(Ref2) end, %% Check key is deleted ?assertEqual(not_found, keydir_get(Ref1, Key)), keydir_release(Ref1), keydir_release(Ref2), ok. keydir_del_put_while_pending_test_() -> {timeout, 60, fun keydir_del_put_while_pending_test2/0}. keydir_del_put_while_pending_test2() -> Name = "k_del_put_while_pending_test", {not_ready, Ref1} = keydir_new(Name), Key = <<"abc">>, keydir_mark_ready(Ref1), {ready, Ref2} = keydir_new(Name), T = bitcask_time:tstamp(), try %% Start keyfold iterator on Ref2 ok = keydir_itr(Ref2, -1, -1), %% Delete Key ?assertEqual(ok, keydir_remove(Ref1, Key)), ok = keydir_put(Ref1, Key, 0, 1234, 0, T+2, bitcask_time:tstamp()), ?assertEqual(#bitcask_entry{key = Key, file_id = 0, total_sz = 1234, offset = <<0:64/unsigned-native>>, tstamp = T+2}, keydir_get_int(Ref1, Key, T+2)), %% Keep iterating on Ref2 and check result is [] it was started after iter Fun = fun(IterKey, Acc) -> [IterKey | Acc] end, ?assertEqual([], keydir_fold_cont(keydir_itr_next(Ref2), Ref2, Fun, [])) after %% End iteration ok = keydir_itr_release(Ref2) end, %% Check key is still present ?assertEqual(#bitcask_entry{key = Key, file_id = 0, total_sz = 1234, offset = <<0:64/unsigned-native>>, tstamp = T+2}, keydir_get_int(Ref1, Key, 16#ffffffffffffffff)). keydir_multi_put_during_itr_test_() -> {timeout, 60, fun keydir_multi_put_during_itr_test2/0}. keydir_multi_put_during_itr_test2() -> {not_ready, Ref} = bitcask_nifs:keydir_new("t"), bitcask_nifs:keydir_mark_ready(Ref), bitcask_nifs:keydir_put(Ref, <<"k">>, 123, 1, 0, 1, bitcask_time:tstamp()), bitcask_nifs:keydir_itr(Ref, 0, 0), bitcask_nifs:keydir_put(Ref, <<"k">>, 123, 2, 10, 2, bitcask_time:tstamp()), bitcask_nifs:keydir_put(Ref, <<"k">>, 123, 3, 20, 3, bitcask_time:tstamp()), bitcask_nifs:keydir_put(Ref, <<"k">>, 123, 4, 30, 4, bitcask_time:tstamp()), bitcask_nifs:keydir_itr_release(Ref). keydir_itr_out_of_date_test_() -> {timeout, 60, fun keydir_itr_out_of_date_test2/0}. keydir_itr_out_of_date_test2() -> Name = "keydir_itr_out_of_date_test", {not_ready, Ref1} = bitcask_nifs:keydir_new(Name), bitcask_nifs:keydir_mark_ready(Ref1), ok = bitcask_nifs:keydir_itr_int(Ref1, 1000000, 0, 0), put_till_frozen(Ref1, Name), {ready, Ref2} = bitcask_nifs:keydir_new(Name), %% now() will have ensured a new usecs for keydir_itr/3 - check out of date immediately ?assertEqual(out_of_date, bitcask_nifs:keydir_itr_int(Ref2, 1000001, 0, 0)), keydir_itr_release(Ref1), ?assertEqual(ok, receive ready -> ok after 1000 -> timeout end). put_till_frozen(R, Name) -> bitcask_nifs:keydir_put(R, crypto:strong_rand_bytes(32), 0, 1234, 0, 1, bitcask_time:tstamp()), {ready, Ref2} = bitcask_nifs:keydir_new(Name), %%?debugFmt("Putting", []), case bitcask_nifs:keydir_itr_int(Ref2, 2000001, 0, 0) of ok -> %%?debugFmt("keydir still OK", []), bitcask_nifs:keydir_itr_release(Ref2), put_till_frozen(R, Name); out_of_date -> %%?debugFmt("keydir now frozen", []), bitcask_nifs:keydir_itr_release(Ref2), ok end. keydir_itr_many_pending_test_() -> {timeout, 60, fun keydir_itr_many_pending_test2/0}. keydir_itr_many_pending_test2() -> Name = "keydir_itr_many_out_of_date_test", {not_ready, Ref1} = bitcask_nifs:keydir_new(Name), bitcask_nifs:keydir_mark_ready(Ref1), ok = bitcask_nifs:keydir_itr_int(Ref1, 1000000, 0, 0), put_till_frozen(Ref1, Name), Me = self(), F = fun() -> {ready, Ref2} = bitcask_nifs:keydir_new(Name), out_of_date = bitcask_nifs:keydir_itr_int(Ref2, 1000001, 0, 0), Me ! {ready, self()}, receive ready -> Me ! {done, self()} end end, %% Check the pending_awaken array grows nicely Pids = [proc_lib:spawn_link(F) || _X <- lists:seq(1, 100)], ?assertEqual(lists:usort([receive {ready, Pid} -> ready after 500 -> {timeout, Pid} end || Pid <- Pids]), [ready]), %% Wake them up and check them. keydir_itr_release(Ref1), ?assertEqual(lists:usort([receive {done, Pid} -> ok after 500 -> {timeout, Pid} end || Pid <- Pids]), [ok]). clear_recv_buffer(Ct) -> receive _ -> clear_recv_buffer(Ct+1) after 0 -> ok %%?debugFmt("cleared ~p msgs", [Ct]) end. keydir_wait_pending_test_() -> {timeout, 60, fun keydir_wait_pending_test2/0}. keydir_wait_pending_test2() -> clear_recv_buffer(0), Name = "keydir_wait_pending_test", {not_ready, Ref1} = keydir_new(Name), keydir_mark_ready(Ref1), %% Begin iterating ok = bitcask_nifs:keydir_itr(Ref1, 0, 0), put_till_frozen(Ref1, Name), %% Spawn a process to wait on pending Me = self(), F = fun() -> {ready, Ref2} = keydir_new(Name), Me ! waiting, keydir_wait_pending(Ref2), Me ! waited end, spawn(F), %% Make sure it starts ok = receive waiting -> ok after 1000 -> start_err end, %% Give it a chance to call keydir_wait_pending then blocks timer:sleep(200), nothing = receive Msg -> {msg, Msg} after 1000 -> nothing end, %% End iterating - make sure the waiter wakes up keydir_itr_release(Ref1), ok = receive waited -> ok after 1000 -> timeout_err end. -ifdef(EQC). -define(POW_2(N), trunc(math:pow(2, N))). -define(QC_OUT(P), eqc:on_output(fun(Str, Args) -> io:format(user, Str, Args) end, P)). g_uint32() -> choose(0, ?POW_2(31)). g_uint64() -> choose(0, ?POW_2(62)). g_entry() -> #bitcask_entry{ key = non_empty(binary()), file_id = g_uint32(), total_sz = g_uint32(), offset = g_uint64(), tstamp = g_uint32() }. prop_keydir_get_put() -> ?FORALL(E, g_entry(), begin {ok, Ref} = keydir_new(), ok = keydir_put(Ref, E#bitcask_entry.key, E#bitcask_entry.file_id, E#bitcask_entry.total_sz, E#bitcask_entry.offset, E#bitcask_entry.tstamp, bitcask_time:tstamp()), E2 = keydir_get(Ref, E#bitcask_entry.key), keydir_release(Ref), ?assertEqual(E, E2), true end). -endif. -ifdef(TIMING_TEST_NOT_EUNIT_TEST). -define(YOO_ITERS, 10). yoo_start_test_() -> {timeout, 60, fun() -> io:format(user, "My OS pid is ~s\n", [os:getpid()]), timer:sleep(15*1000) end}. yoo_test_1M_c1k_d0_() -> {timeout, 6666, fun() -> [yoo(1000000, 1000, 0) || _ <- lists:seq(1,?YOO_ITERS)] end}. yoo_test_1M_c250k_d0_() -> {timeout, 6666, fun() -> [yoo(1000000, 250000, 0) || _ <- lists:seq(1,?YOO_ITERS)] end}. yoo_test_1M_c900k_d0_() -> {timeout, 6666, fun() -> [yoo(1000000, 900000, 0) || _ <- lists:seq(1,?YOO_ITERS)] end}. yoo_test_1M_c0_d1k_() -> {timeout, 6666, fun() -> [yoo(1000000, 1000, 0) || _ <- lists:seq(1,?YOO_ITERS)] end}. yoo_test_1M_c0_d250k_() -> {timeout, 6666, fun() -> [yoo(1000000, 250000, 0) || _ <- lists:seq(1,?YOO_ITERS)] end}. yoo_test_1M_c0_d900k_() -> {timeout, 6666, fun() -> [yoo(1000000, 900000, 0) || _ <- lists:seq(1,?YOO_ITERS)] end}. yoo(NumKeys, NumChange, NumDelete) -> _ = (catch folsom:start()), timer:sleep(200), catch folsom_metrics:delete_metric(foo), folsom_metrics:new_histogram(foo, uniform, 9981239823), {ok, Ref} = keydir_new(), try T0 = os:timestamp(), [ok = keydir_put(Ref, <>, 0, 0, X, 0, bitcask_time:tstamp()) || X <- lists:seq(1, NumKeys)], T1 = os:timestamp(), ok = keydir_itr(Ref, -1, -1), T2 = os:timestamp(), [ok = keydir_put(Ref, <>, 1, 0, X, 0, bitcask_time:tstamp()) || X <- lists:seq(1, NumChange)], [ok = keydir_remove(Ref, <>, bitcask_time:tstamp()) || X <- lists:seq(NumKeys - NumDelete, NumKeys)], T3 = os:timestamp(), ok = keydir_itr_release(Ref), %% This method's use of list comprehension + lists:seq(1,LargeNum) %% generates enough garbage to cause tail latency outliers %% that are really annoying. %% %% OpList = lists:seq(1, NumKeys), %% Get = fun(Seq) -> %% erlang:garbage_collect(), %% dyntrace:pn(0, 1), %% [begin %% dyntrace:pn(1, 1), %% %% T4 = os:timestamp(), %% _ = keydir_get(Ref, <>, 1), %% %% T5 = os:timestamp(), %% dyntrace:pn(1, 0), %% %% Elapsed = timer:now_diff(T5, T4), %% %% dyntrace:pn(900, Elapsed), %% %% if Elapsed > 16384 -> io:format(user, "16+x", []); Elapsed > 8192 -> io:format(user, "8x", []); Elapsed > 4096 -> io:format(user, "4x", []); Elapsed > 2048 -> io:format(user, "2x", []); Elapsed > 1024 -> io:format(user, "x", []); true -> ok end, %% %% folsom_metrics_histogram:update(foo, Elapsed) %% ok %% end || X <- OpList], %% dyntrace:pn(0, 0), %% QQ = folsom_metrics:get_histogram_statistics(foo), %% catch folsom_metrics:delete_metric(foo), %% folsom_metrics:new_histogram(foo, uniform, 9981239823), %% {Seq, [X || X = {Tag, _} <- QQ, Tag == max orelse Tag == percentile]} %% end, GetAndTime = fun(X) -> dyntrace:pn(1, 1), %% T4 = os:timestamp(), _ = keydir_get(Ref, <>, 1), %% T5 = os:timestamp(), dyntrace:pn(1, 0) end, Get = fun(Seq) -> erlang:garbage_collect(), dyntrace:pn(0, 1, Seq), iter(GetAndTime, NumKeys), dyntrace:pn(0, 0, Seq), ok end, [io:format(user, "~p\n", [Get(Seq)]) || Seq <- lists:seq(1,4)], ok after catch folsom_metrics:delete_metric(foo), ok = keydir_release(Ref) end. iter(Fun, 0) -> ok; iter(Fun, N) -> Fun(N), iter(Fun, N-1). -endif. % TIMING_TEST_NOT_EUNIT_TEST -endif. % EQC bitcask-2.1.0/src/bitcask_bump.erl0000644000232200023220000000205013655023466017445 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_bump). -export([big/0, small/0]). -ifdef(PULSE). big() -> ok. small() -> ok. -else. big() -> erlang:bump_reductions(1900). small() -> erlang:bump_reductions(500). -endif. bitcask-2.1.0/src/bitcask_lockops.erl0000644000232200023220000001442213655023466020162 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_lockops). -export([acquire/2, release/1, delete_stale_lock/2, read_activefile/2, write_activefile/2]). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_side_effect, [{file, '_', '_'}, {bitcask_nifs, '_', '_'}]}). -endif. -type lock_types() :: merge | write | create. %% @doc Attempt to lock the specified directory with a specific type of lock %% (merge or write). -spec acquire(Type::lock_types(), Dirname::string()) -> {ok, reference()} | {error, any()}. acquire(Type, Dirname) -> LockFilename = lock_filename(Type, Dirname), case bitcask_nifs:lock_acquire(LockFilename, 1) of {ok, Lock} -> %% Successfully acquired our lock. Update the file with our PID. case bitcask_nifs:lock_writedata(Lock, iolist_to_binary([os:getpid(), " \n"])) of ok -> {ok, Lock}; {error, _} = Else -> Else end; {error, eexist} -> %% Lock file already exists, but may be stale. Delete it if it's stale %% and try to acquire again case delete_stale_lock(LockFilename) of ok -> acquire(Type, Dirname); not_stale -> {error, locked} end; {error, Reason} -> {error, Reason} end. %% @doc Release a previously acquired write/merge lock. -spec release(reference()) -> ok. release(Lock) -> bitcask_nifs:lock_release(Lock). %% @doc Read the active filename stored in a given lockfile. -spec read_activefile(Type::lock_types(), Dirname::string()) -> string() | undefined. read_activefile(Type, Dirname) -> LockFilename = lock_filename(Type, Dirname), case bitcask_nifs:lock_acquire(LockFilename, 0) of {ok, Lock} -> try case read_lock_data(Lock) of {ok, _Pid, ActiveFile} -> ActiveFile; _ -> undefined end after bitcask_nifs:lock_release(Lock) end; {error, _Reason} -> undefined end. %% @doc Write a new active filename to an open lockfile. -spec write_activefile(reference(), string()) -> {ftruncate_error, integer()} | {pwrite_error, integer()} | ok | {error, lock_not_writable}. write_activefile(Lock, ActiveFilename) -> Contents = iolist_to_binary([os:getpid(), " ", ActiveFilename, "\n"]), bitcask_nifs:lock_writedata(Lock, Contents). delete_stale_lock(Type, Dirname) -> delete_stale_lock(lock_filename(Type,Dirname)). %% =================================================================== %% Internal functions %% =================================================================== lock_filename(Type, Dirname) -> filename:join(Dirname, lists:concat(["bitcask.", Type, ".lock"])). read_lock_data(Lock) -> case bitcask_nifs:lock_readdata(Lock) of {ok, Contents} -> case re:run(Contents, "([0-9]+) (.*)\n", [{capture, all_but_first, list}]) of {match, [OsPid, []]} -> {ok, OsPid, undefined}; {match, [OsPid, LockedFilename]} -> {ok, OsPid, LockedFilename}; nomatch -> {error, {invalid_data, Contents}} end; {error, Reason} -> {error, Reason} end. os_pid_exists(Pid) -> %% Use kill -0 trick to determine if a process exists. This _should_ be %% portable across all unix variants we are interested in. [] == os:cmd(io_lib:format("kill -0 ~s", [Pid])). delete_stale_lock(Filename) -> %% Open the lock for read-only access. We do this to avoid race-conditions %% with other O/S processes that are attempting the same task. Opening a %% fd and holding it open until AFTER the unlink ensures that the file we %% initially read is the same one we are deleting. case bitcask_nifs:lock_acquire(Filename, 0) of {ok, Lock} -> try case read_lock_data(Lock) of {ok, OsPid, _LockedFilename} -> case os_pid_exists(OsPid) of true -> %% The lock IS NOT stale, so we can't delete it. not_stale; false -> %% The lock IS stale; delete the file. _ = file:delete(Filename), ok end; {error, Reason} -> error_logger:error_msg("Failed to read lock data from ~s: ~p\n", [Filename, Reason]), not_stale end after bitcask_nifs:lock_release(Lock) end; {error, enoent} -> %% Failed to open the lock for reading, but only because it doesn't exist %% any longer. Treat this as a successful delete; the lock file existed %% when we started. ok; {error, Reason} -> %% Failed to open the lock for reading due to other errors. error_logger:error_msg("Failed to open lock file ~s: ~p\n", [Filename, Reason]), not_stale end. bitcask-2.1.0/src/bitcask.app.src0000644000232200023220000000573313655023466017221 0ustar debalancedebalance{application, bitcask, [ {description, "Yet another key/value storage engine"}, {vsn, "2.0.3"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib ]}, {mod, {bitcask_app, []}}, {env, [ %% Default max file size (in bytes) {max_file_size, 16#80000000}, % 2GB default {tombstone_version, 2}, %% Wait time to open a keydir (in seconds) {open_timeout, 4}, %% Strategies available for syncing data to disk: %% * none - let the O/S decide %% * o_sync - use the O_SYNC flag to sync each write %% * {seconds, N} - call bitcask:sync/1 every N seconds %% %% Note that for the {seconds, N} strategy, it is up to the %% API caller to execute the call on the interval. This config %% option is (currently) a convenient placeholder for calling %% applications. {sync_strategy, none}, %% Require the CRC to be present at the end of hintfiles. %% Bitcask defaults to a backward compatible mode where %% old hint files will still be accepted without them. %% It is safe to set this true for new deployments and will %% become the default setting in a future release. {require_hint_crc, false}, %% Merge window. Span of hours during which merge is acceptable. %% * {Start, End} - Hours during which merging is permitted %% * always - Merging is always permitted (default) %% * never - Merging is never permitted {merge_window, always}, %% Merge trigger variables. Files exceeding ANY of these %% values will cause bitcask:needs_merge/1 to return true. %% {frag_merge_trigger, 60}, % >= 60% fragmentation {dead_bytes_merge_trigger, 536870912}, % Dead bytes > 512 MB %% Merge thresholds. Files exceeding ANY of these values %% will be included in the list of files marked for merging %% by bitcask:needs_merge/1. %% {frag_threshold, 40}, % >= 40% fragmentation {dead_bytes_threshold, 134217728}, % Dead bytes > 128 MB {small_file_threshold, 10485760}, % File is < 10 MB %% Fold keys thresholds. max_fold_age will reuse the keydir if %% another fold was started less than max_fold_age ago and there %% were less than max_fold_puts updates. Otherwise it will %% wait until all current fold keys complete and then start. %% Set either option to -1 to disable. {max_fold_age, -1}, % age in micro seconds (unlimited) {max_fold_puts, 0}, % maximum number of updates %% Data expiration can be caused by setting this to a %% positive value. If so, items older than the value %% will be discarded. {expiry_secs, -1} ]} ]}. bitcask-2.1.0/src/bitcask.erl0000644000232200023220000042542013655023466016434 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask). -export([open/1, open/2, close/1, close_write_file/1, get/2, put/3, delete/2, sync/1, list_keys/1, fold_keys/3, fold_keys/6, fold/3, fold/6, iterator/3, iterator_next/1, iterator_release/1, merge/1, merge/2, merge/3, needs_merge/1, needs_merge/2, is_frozen/1, is_empty_estimate/1, status/1]). -export([get_opt/2, get_filestate/2, is_tombstone/1]). -export([has_pending_delete_bit/1]). % For EUnit tests -include_lib("kernel/include/file.hrl"). -include("bitcask.hrl"). -include("stacktrace.hrl"). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -compile([export_all, nowarn_export_all]). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_side_effect, [{bitcask_nifs, '_', '_'}]}). -define(OPEN_FOLD_RETRIES, 100). -else. -define(OPEN_FOLD_RETRIES, 3). -endif. -ifdef(TEST). -compile([export_all, nowarn_export_all]). -include_lib("eunit/include/eunit.hrl"). -include_lib("kernel/include/file.hrl"). -export([leak_t0/0, leak_t1/0]). -endif. %% In the real world, 1 or 2 retries is usually sufficient. In the %% world of QuickCheck, however, QC can create some diabolical %% races, so use a diabolical number. -define(DIABOLIC_BIG_INT, 100). %% In an EQC testing scenario, poll_for_merge_lock() may have failed %% This atom is the signal that it failed but is harmless in this situation. -define(POLL_FOR_MERGE_LOCK_PSEUDOFAILURE, pseudo_failure). %% @type bc_state(). -record(bc_state, {dirname :: string(), write_file :: #filestate{} | fresh | undefined, % File for writing write_lock :: reference() | undefined, % Reference to write lock read_files = [] :: [#filestate{}], % Files opened for reading max_file_size = 0 :: integer(), % Max. size of a written file opts = [] :: list(), % Original options used to open the bitcask key_transform :: function() | undefined, keydir :: reference(), % Key directory read_write_p = 0 :: integer(), % integer() avoids atom -> NIF % What tombstone style to write, for testing purposes only. % 0 = old style without file id, 2 = new style with file id tombstone_version = 2 :: 0 | 2 }). -ifdef(namespaced_types). -type bitcask_set() :: sets:set(). -else. -type bitcask_set() :: set(). -endif. -record(mstate, { dirname :: string(), merge_lock :: reference(), max_file_size :: integer(), input_files :: [#filestate{}], input_file_ids :: bitcask_set(), min_file_id :: non_neg_integer(), tombstone_write_files :: [#filestate{}], out_file :: 'fresh' | #filestate{}, merge_coverage :: prefix | partial | full, live_keydir :: reference(), del_keydir :: reference(), expiry_time :: integer(), expiry_grace_time :: integer(), key_transform :: function(), read_write_p :: integer(), % integer() avoids atom -> NIF opts :: list(), delete_files :: [#filestate{}]}). %% A bitcask is a directory containing: %% * One or more data files - {integer_timestamp}.bitcask.data %% * A write lock - bitcask.write.lock (Optional) %% * A merge lock - bitcask.merge.lock (Optional) %% @doc Open a new or existing bitcask datastore for read-only access. -spec open(Dirname::string()) -> reference() | {error, timeout}. open(Dirname) -> open(Dirname, []). %% @doc Open a new or existing bitcask datastore with additional options. -spec open(Dirname::string(), Opts::[_]) -> reference() | {error, timeout}. open(Dirname, Opts) -> %% Make sure bitcask app is started so we can pull defaults from env ok = start_app(), %% Make sure the directory exists ok = filelib:ensure_dir(filename:join(Dirname, "bitcask")), %% If the read_write option is set, attempt to release any stale write lock. %% Do this first to avoid unnecessary processing of files for reading. WritingFile = case proplists:get_bool(read_write, Opts) of true -> %% If the lock file is not stale, we'll continue initializing %% and loading anyway: if later someone tries to write %% something, that someone will get a write_locked exception. _ = bitcask_lockops:delete_stale_lock(write, Dirname), fresh; false -> undefined end, %% Get the max file size parameter from opts MaxFileSize = get_opt(max_file_size, Opts), %% Get the number of seconds we are willing to wait for the keydir init to timeout WaitTime = timer:seconds(get_opt(open_timeout, Opts)), %% Set the key transform for this cask KeyTransformFun = get_key_transform(get_opt(key_transform, Opts)), %% Type of tombstone to write, for testing. TombstoneVersion = get_opt(tombstone_version, Opts), %% Loop and wait for the keydir to come available. ReadWriteP = WritingFile /= undefined, ReadWriteI = case ReadWriteP of true -> 1; false -> 0 end, case init_keydir(Dirname, WaitTime, ReadWriteP, KeyTransformFun) of {ok, KeyDir, ReadFiles} -> %% Ensure that expiry_secs is in Opts and not just application env ExpOpts = [{expiry_secs,get_opt(expiry_secs,Opts)}|Opts], Ref = make_ref(), erlang:put(Ref, #bc_state {dirname = Dirname, read_files = ReadFiles, write_file = WritingFile, % |undefined|fresh write_lock = undefined, max_file_size = MaxFileSize, opts = ExpOpts, keydir = KeyDir, key_transform = KeyTransformFun, tombstone_version = TombstoneVersion, read_write_p = ReadWriteI}), Ref; {error, Reason} -> {error, Reason} end. %% @doc Close a bitcask data store and flush any pending writes to disk. -spec close(reference()) -> ok. close(Ref) -> State = get_state(Ref), erlang:erase(Ref), %% Cleanup the write file and associated lock case State#bc_state.write_file of undefined -> ok; fresh -> ok; WriteFile -> _ = bitcask_fileops:close_for_writing(WriteFile), ok = bitcask_lockops:release(State#bc_state.write_lock) end, %% Manually release the keydir. If, for some reason, this failed GC would %% still get the job done. bitcask_nifs:keydir_release(State#bc_state.keydir), %% Clean up all the reading files bitcask_fileops:close_all(State#bc_state.read_files), ok. %% @doc Close the currently active writing file; mostly for testing purposes close_write_file(Ref) -> #bc_state { write_file = WriteFile} = State = get_state(Ref), case WriteFile of undefined -> ok; fresh -> ok; _ -> LastWriteFile = bitcask_fileops:close_for_writing(WriteFile), ok = bitcask_lockops:release(State#bc_state.write_lock), S2 = State#bc_state { write_file = fresh, read_files = [LastWriteFile | State#bc_state.read_files]}, put_state(Ref, S2) end. %% @doc Retrieve a value by key from a bitcask datastore. -spec get(reference(), binary()) -> not_found | {ok, Value::binary()} | {error, Err::term()}. get(Ref, Key) -> get(Ref, Key, 2). -spec get(reference(), binary(), integer()) -> not_found | {ok, Value::binary()} | {error, Err::term()}. get(_Ref, _Key, 0) -> {error, nofile}; get(Ref, Key, TryNum) -> State = get_state(Ref), case bitcask_nifs:keydir_get(State#bc_state.keydir, Key) of not_found -> not_found; E when is_record(E, bitcask_entry) -> case E#bitcask_entry.tstamp < expiry_time(State#bc_state.opts) of true -> %% Expired entry; remove from keydir case bitcask_nifs:keydir_remove(State#bc_state.keydir, Key, E#bitcask_entry.tstamp, E#bitcask_entry.file_id, E#bitcask_entry.offset) of ok -> not_found; already_exists -> % Updated since last read, try again. get(Ref, Key, TryNum-1) end; false -> %% HACK: Use a fully-qualified call to get_filestate/2 so that %% we can intercept calls w/ Pulse tests. case ?MODULE:get_filestate(E#bitcask_entry.file_id, State) of {error, enoent} -> %% merging deleted file between keydir_get and here get(Ref, Key, TryNum-1); {error, _} = Else -> Else; {Filestate, S2} -> put_state(Ref, S2), case bitcask_fileops:read(Filestate, E#bitcask_entry.offset, E#bitcask_entry.total_sz) of {ok, _Key, Value} -> case is_tombstone(Value) of true -> not_found; false -> {ok, Value} end; {error, eof} -> not_found; {error, _} = Err -> Err end end end end. %% @doc Store a key and value in a bitcase datastore. put(Ref, Key, Value) -> #bc_state { write_file = WriteFile } = State = get_state(Ref), %% Make sure we have a file open to write case WriteFile of undefined -> throw({error, read_only}); _ -> ok end, try {Ret, State1} = do_put(Key, Value, State, ?DIABOLIC_BIG_INT, undefined), put_state(Ref, State1), Ret catch throw:{unrecoverable, Error, State2} -> put_state(Ref, State2), {error, Error} end. %% @doc Delete a key from a bitcask datastore. -spec delete(reference(), Key::binary()) -> ok. delete(Ref, Key) -> put(Ref, Key, tombstone). %% @doc Force any writes to sync to disk. -spec sync(reference()) -> ok. sync(Ref) -> State = get_state(Ref), case (State#bc_state.write_file) of undefined -> ok; fresh -> ok; File -> ok = bitcask_fileops:sync(File) end. %% @doc List all keys in a bitcask datastore. -spec list_keys(reference()) -> [Key::binary()] | {error, any()}. list_keys(Ref) -> fold_keys(Ref, fun(#bitcask_entry{key=K},Acc) -> [K|Acc] end, []). %% @doc Fold over all keys in a bitcask datastore. %% Must be able to understand the bitcask_entry record form. -spec fold_keys(reference(), Fun::fun(), Acc::term()) -> term() | {error, any()}. fold_keys(Ref, Fun, Acc0) -> State = get_state(Ref), MaxAge = get_opt(max_fold_age, State#bc_state.opts) * 1000, % convert from ms to us MaxPuts = get_opt(max_fold_puts, State#bc_state.opts), fold_keys(Ref, Fun, Acc0, MaxAge, MaxPuts, false). %% @doc Fold over all keys in a bitcask datastore with limits on how out of date %% the keydir is allowed to be. %% Must be able to understand the bitcask_entry record form. -spec fold_keys(reference(), Fun::fun(), Acc::term(), non_neg_integer() | undefined, non_neg_integer() | undefined, boolean()) -> term() | {error, any()}. fold_keys(Ref, Fun, Acc0, MaxAge, MaxPut, SeeTombstonesP) -> %% Fun should be of the form F(#bitcask_entry, A) -> A ExpiryTime = expiry_time((get_state(Ref))#bc_state.opts), RealFun = fun(BCEntry, Acc) -> Key = BCEntry#bitcask_entry.key, case BCEntry#bitcask_entry.tstamp < ExpiryTime of true -> Acc; false -> case BCEntry#bitcask_entry.total_sz - (?HEADER_SIZE + size(Key)) of Ss when ?IS_TOMBSTONE_SIZE(Ss) -> %% might be a deleted record, so check case ?MODULE:get(Ref, Key) of not_found when not SeeTombstonesP -> Acc; not_found when SeeTombstonesP -> Fun({tombstone, BCEntry}, Acc); _ -> Fun(BCEntry, Acc) end; _ -> Fun(BCEntry, Acc) end end end, bitcask_nifs:keydir_fold((get_state(Ref))#bc_state.keydir, RealFun, Acc0, MaxAge, MaxPut). %% @doc fold over all K/V pairs in a bitcask datastore. %% Fun is expected to take F(K,V,Acc0) -> Acc -spec fold(reference() | tuple(), fun((binary(), binary(), any()) -> any()), any()) -> any() | {error, any()}. fold(Ref, Fun, Acc0) when is_reference(Ref)-> State = get_state(Ref), fold(State, Fun, Acc0); fold(State, Fun, Acc0) -> MaxAge = get_opt(max_fold_age, State#bc_state.opts) * 1000, % convert from ms to us MaxPuts = get_opt(max_fold_puts, State#bc_state.opts), SeeTombstonesP = get_opt(fold_tombstones, State#bc_state.opts) /= undefined, fold(State, Fun, Acc0, MaxAge, MaxPuts, SeeTombstonesP). %% @doc fold over all K/V pairs in a bitcask datastore specifying max age/updates of %% the frozen keystore. %% Fun is expected to take F(K,V,Acc0) -> Acc -spec fold(reference() | tuple(), fun((binary(), binary(), any()) -> any()), any(), non_neg_integer() | undefined, non_neg_integer() | undefined, boolean()) -> any() | {error, any()}. fold(Ref, Fun, Acc0, MaxAge, MaxPut, SeeTombstonesP) when is_reference(Ref)-> State = get_state(Ref), fold(State, Fun, Acc0, MaxAge, MaxPut, SeeTombstonesP); fold(State, Fun, Acc0, MaxAge, MaxPut, SeeTombstonesP) -> KT = State#bc_state.key_transform, FrozenFun = fun() -> case open_fold_files(State#bc_state.dirname, State#bc_state.keydir, ?OPEN_FOLD_RETRIES) of {ok, Files, FoldEpoch} -> ExpiryTime = expiry_time(State#bc_state.opts), SubFun = fun(K0,V,TStamp,{_FN,FTS,Offset,_Sz},Acc) -> K = try KT(K0) catch KeyTxErr -> {key_tx_error, {K0, KeyTxErr}} end, case {K, (TStamp < ExpiryTime)} of {{key_tx_error, TxErr}, _} -> error_logger:error_msg("Error converting key ~p: ~p", [K0, TxErr]), Acc; {_, true} -> Acc; {_, false} -> case bitcask_nifs:keydir_get( State#bc_state.keydir, K, FoldEpoch) of not_found -> Acc; E when is_record(E, bitcask_entry) -> case Offset =:= E#bitcask_entry.offset andalso TStamp =:= E#bitcask_entry.tstamp andalso FTS =:= E#bitcask_entry.file_id of false -> Acc; true when SeeTombstonesP -> Fun({tombstone, K},V,Acc); true when not SeeTombstonesP -> case is_tombstone(V) of true -> Acc; false -> Fun(K,V,Acc) end end end end end, subfold(SubFun,Files,Acc0); {error, Reason} -> {error, Reason} end end, KeyDir = State#bc_state.keydir, bitcask_nifs:keydir_frozen(KeyDir, FrozenFun, MaxAge, MaxPut). %% %% Get a list of readable files and attempt to open them for a fold. If we can't %% open any one of the files, get a fresh list of files and try again. %% open_fold_files(_Dirname, _Keydir, 0) -> {error, max_retries_exceeded_for_fold}; open_fold_files(Dirname, Keydir, Count) -> try {Epoch, CurrentFiles} = current_files(Dirname, Keydir), Filenames = [F#file_status.filename || F <- CurrentFiles], case open_files(Filenames, []) of {ok, Files} -> {ok, Files, Epoch}; {error, ErrFile, Err} -> maybe_log_missing_file(Dirname, Keydir, ErrFile, Err), open_fold_files(Dirname, Keydir, Count-1) end catch ?_exception_(X, Y, StackToken) -> {error, {X,Y, ?_get_stacktrace_(StackToken)}} end. maybe_log_missing_file(Dirname, Keydir, ErrFile, enoent) -> case is_current_file(Dirname, Keydir, ErrFile) of true -> error_logger:error_msg("Unexpectedly missing file ~s", [ErrFile]), FileId = bitcask_fileops:file_tstamp(ErrFile), %% Forget it to avoid retrying opening it _ = bitcask_nifs:keydir_trim_fstats(Keydir, [FileId]), ok; false -> ok end; maybe_log_missing_file(_, _, _, _) -> ok. is_current_file(Dirname, Keydir, Filename) -> FileId = bitcask_fileops:file_tstamp(Filename), {_Epoch, CurrentFiles} = current_files(Dirname, Keydir), lists:any(fun(F) -> bitcask_fileops:file_tstamp(F#file_status.filename) == FileId end, CurrentFiles). %% %% Open a list of filenames; if any one of them fails to open, error out. %% open_files([], Acc) -> {ok, lists:reverse(Acc)}; open_files([Filename | Rest], Acc) -> case bitcask_fileops:open_file(Filename) of {ok, Fd} -> open_files(Rest, [Fd | Acc]); {error, Err} -> bitcask_fileops:close_all(Acc), {error, Filename, Err} end. %% %% Apply fold function to a single bitcask file; results are accumulated in %% Acc %% subfold(_SubFun,[],Acc) -> Acc; subfold(SubFun,[FD | Rest],Acc0) -> Acc2 = try bitcask_fileops:fold(FD, SubFun, Acc0) of Acc1 -> Acc1 catch throw:{fold_error, Error, _PartialAcc} -> error_logger:error_msg("subfold: skipping file ~s: ~p\n", [FD#filestate.filename, Error]), Acc0 after bitcask_fileops:close(FD) end, subfold(SubFun, Rest, Acc2). %% @doc Start entry iterator -spec iterator(reference(), integer(), integer()) -> ok | out_of_date | {error, iteration_in_process}. iterator(Ref, MaxAge, MaxPuts) -> KeyDir = (get_state(Ref))#bc_state.keydir, bitcask_nifs:keydir_itr(KeyDir, MaxAge, MaxPuts). %% @doc Get next entry from the iterator -spec iterator_next(reference()) -> #bitcask_entry{} | {error, iteration_not_started} | allocation_error | not_found. iterator_next(Ref) -> KeyDir = (get_state(Ref))#bc_state.keydir, bitcask_nifs:keydir_itr_next(KeyDir). %% @doc Release iterator -spec iterator_release(reference()) -> ok. iterator_release(Ref) -> KeyDir = (get_state(Ref))#bc_state.keydir, bitcask_nifs:keydir_itr_release(KeyDir). %% @doc Merge several data files within a bitcask datastore %% into a more compact form. -spec merge(Dirname::string()) -> ok | {error, any()}. merge(Dirname) -> merge(Dirname, []). %% @doc Merge several data files within a bitcask datastore %% into a more compact form. -spec merge(Dirname::string(), Opts::[_]) -> ok | {error, any()}. merge(Dirname, Opts) -> merge(Dirname, Opts, {readable_files(Dirname), []}). %% @doc Merge several data files within a bitcask datastore %% into a more compact form. -spec merge(Dirname::string(), Opts::[_], {FilesToMerge::[string()],FilesToDelete::[string()]}) -> ok | {error, any()}. merge(_Dirname, _Opts, []) -> ok; merge(Dirname,Opts,FilesToMerge) when is_list(FilesToMerge) -> merge(Dirname,Opts,{FilesToMerge,[]}); merge(_Dirname, _Opts, {[],_}) -> ok; merge(Dirname, Opts, {FilesToMerge0, ExpiredFiles0}) -> try %% Make sure bitcask app is started so we can pull defaults from env ok = start_app(), %% Filter the files to merge and ensure that they all exist. It's %% possible in some circumstances that we'll get an out-of-date %% list of files. FilesToMerge = [F || F <- FilesToMerge0, bitcask_fileops:is_file(F)], ExpiredFiles = [F || F <- ExpiredFiles0, bitcask_fileops:is_file(F)], merge1(Dirname, Opts, FilesToMerge, ExpiredFiles) catch throw:Reason -> Reason; ?_exception_(X, Y, StackToken) -> {error, {generic_failure, X, Y, ?_get_stacktrace_(StackToken)}} end. %% Inner merge function, assumes that bitcask is running and all files exist. merge1(_Dirname, _Opts, [], []) -> ok; merge1(Dirname, Opts, FilesToMerge0, ExpiredFiles) -> KT = get_key_transform(get_opt(key_transform, Opts)), %% Try to lock for merging case bitcask_lockops:acquire(merge, Dirname) of {ok, Lock} -> ok; {error, Reason} -> Lock = undefined, throw({error, {merge_locked, Reason, Dirname}}) end, %% Get the live keydir case bitcask_nifs:maybe_keydir_new(Dirname) of {ready, LiveKeyDir} -> %% Simplest case; a key dir is already available and %% loaded. Go ahead and open just the files we wish to %% merge InFiles0 = [begin %% Handle open errors gracefully. QuickCheck %% plus PULSE showed that there are races where %% the open below can fail. case bitcask_fileops:open_file(F) of {ok, Fstate} -> Fstate; {error, _} -> skip end end || F <- FilesToMerge0], InFiles1 = [F || F <- InFiles0, F /= skip]; {error, not_ready} -> %% Someone else is loading the keydir, or this cask isn't open. %% We'll bail here and try again later. ok = bitcask_lockops:release(Lock), % Make erlc happy w/ non-local exit LiveKeyDir = undefined, InFiles1 = [], throw({error, not_ready}) end, LiveRef = make_ref(), put_state(LiveRef, #bc_state{dirname = Dirname, keydir = LiveKeyDir}), erlang:erase(LiveRef), {InFiles2,InExpiredFiles} = lists:foldl(fun(F, {InFilesAcc,InExpiredAcc}) -> case lists:member(F#filestate.filename, ExpiredFiles) of false -> {[F|InFilesAcc],InExpiredAcc}; true -> {InFilesAcc,[F|InExpiredAcc]} end end, {[],[]}, InFiles1), %% Test to see if this is a complete or partial merge %% We perform this test now because our efforts to open the input files %% in the InFiles0 list comprehension above may have had an open %% failure. The open(2) shouldn't fail, except, of course, when it %% does, e.g. EMFILE, ENFILE, the OS decides EINTR because "reasons", ... ReadableFiles = lists:usort(readable_files(Dirname) -- ExpiredFiles), FilesToMerge = lists:usort([F#filestate.filename || F <- InFiles2]), MergeCoverage = case FilesToMerge == ReadableFiles of true -> full; false -> case lists:prefix(FilesToMerge, ReadableFiles) of true -> prefix; false -> partial end end, % This sort is very important. The merge expects to visit files in order % to properly detect current values, and could resurrect old values if not. InFiles = lists:sort(fun(#filestate{tstamp=FTL}, #filestate{tstamp=FTR}) -> FTL =< FTR end, InFiles2), InFileIds = sets:from_list([bitcask_fileops:file_tstamp(InFile) || InFile <- InFiles]), MinFileId = if ReadableFiles == [] -> 1; true -> lists:min([bitcask_fileops:file_tstamp(F) || F <- ReadableFiles]) end, %% Initialize the other keydirs we need. {ok, DelKeyDir} = bitcask_nifs:keydir_new(), %% Initialize our state for the merge State = #mstate { dirname = Dirname, merge_lock = Lock, max_file_size = get_opt(max_file_size, Opts), input_files = InFiles, input_file_ids = InFileIds, min_file_id = MinFileId, tombstone_write_files = [], out_file = fresh, % will be created when needed merge_coverage = MergeCoverage, live_keydir = LiveKeyDir, del_keydir = DelKeyDir, expiry_time = expiry_time(Opts), expiry_grace_time = expiry_grace_time(Opts), key_transform = KT, read_write_p = 0, opts = Opts, delete_files = []}, %% Finally, start the merge process ExpiredFilesFinished = expiry_merge(InExpiredFiles, LiveKeyDir, KT, []), State1 = merge_files(State), %% Make sure to close the final output file case State1#mstate.out_file of fresh -> ok; Outfile -> ok = bitcask_fileops:sync(Outfile), ok = bitcask_fileops:close(Outfile) end, _ = [begin ok = bitcask_fileops:sync(TFile), ok = bitcask_fileops:close(TFile) end || TFile <- State1#mstate.tombstone_write_files], %% Close the original input files, schedule them for deletion, %% close keydirs, and release our lock bitcask_fileops:close_all(State#mstate.input_files ++ ExpiredFilesFinished), {_, _, _, {IterGeneration, _, _, _}, _} = bitcask_nifs:keydir_info(LiveKeyDir), DelFiles = [F || F <- State1#mstate.delete_files ++ ExpiredFilesFinished], FileNames = [F#filestate.filename || F <- DelFiles], DelIds = [F#filestate.tstamp || F <- DelFiles], _ = [bitcask_nifs:set_pending_delete(LiveKeyDir, DelId) || DelId <- DelIds], _ = [catch set_pending_delete_bit(F) || F <- FileNames], bitcask_merge_delete:defer_delete(Dirname, IterGeneration, FileNames), %% Explicitly release our keydirs instead of waiting for GC bitcask_nifs:keydir_release(LiveKeyDir), bitcask_nifs:keydir_release(DelKeyDir), ok = bitcask_lockops:release(Lock). %% @doc Predicate which determines whether or not a file should be considered for a merge. consider_for_merge(FragTrigger, DeadBytesTrigger, ExpirationGraceTime) -> fun (F) -> (F#file_status.fragmented >= FragTrigger) orelse (F#file_status.dead_bytes >= DeadBytesTrigger) orelse ((F#file_status.oldest_tstamp > 0) andalso %% means that the file has data (F#file_status.newest_tstamp < ExpirationGraceTime) ) end. -spec needs_merge(reference()) -> {true, {[string()], [string()]}} | false. needs_merge(Ref) -> needs_merge(Ref, []). -spec needs_merge(reference(), proplists:proplist()) -> {true, {[string()], [string()]}} | false. needs_merge(Ref, Opts) -> State = get_state(Ref), {_KeyCount, Summary} = summary_info(Ref), %% Review all the files we currently have open in read_files and %% see if any no longer exist by name (i.e. have been deleted by %% previous merges). Close these files so that we don't leak %% file descriptors. P = fun(F) -> bitcask_fileops:is_file(bitcask_fileops:filename(F)) end, {LiveFiles, DeadFiles} = lists:partition(P, State#bc_state.read_files), %% Close the dead files and accumulate a list for trimming their %% fstats entries. DeadIds0 = [begin bitcask_fileops:close(F), bitcask_fileops:file_tstamp(F) end || F <- DeadFiles], DeadIds = lists:usort(DeadIds0), case bitcask_nifs:keydir_trim_fstats(State#bc_state.keydir, DeadIds) of {ok, 0} -> ok; {ok, Warn} -> error_logger:info_msg("Trimmed ~p non-existent fstat entries", [Warn]); Err -> error_logger:error_msg("Error trimming fstats entries: ~p", [Err]) end, #bc_state{dirname=Dirname} = State, %% Update state with live files put_state(Ref, State#bc_state { read_files = LiveFiles }), Result0 = case explicit_merge_files(Dirname) of [] -> run_merge_triggers(State, Summary); MergeFiles -> {true, {MergeFiles, []}} end, MaxMergeSize = proplists:get_value(max_merge_size, Opts), case Result0 of false -> false; {true, {MergeFiles0, ExpiredFiles}} -> {true, {cap_size(MergeFiles0, MaxMergeSize), ExpiredFiles}} end. -spec cap_size([string()], integer()) -> [string()]. cap_size(Files, MaxSize) -> Result0 = lists:foldl(fun(_, {finished, Acc}) -> {finished, Acc}; (F, {AccSize, InFiles}) -> case bitcask_fileops:read_file_info(F) of {ok, #file_info{size=Size}} when Size+AccSize > MaxSize -> {finished, {AccSize, InFiles}}; {ok, #file_info{size=Size}} -> {Size+AccSize, [F|InFiles]}; _ -> % Can't get size, assume zero (deleted) {AccSize, [F|InFiles]} end end, {0, []}, Files), Result1 = case Result0 of {finished, {_, List}} -> List; {_, List} -> List end, lists:reverse(Result1). %% Reads the list of files to merge from a text file if present. %% The file will be deleted if it doesn't contain unmerged files. -spec explicit_merge_files(Dir :: string()) -> list(string()). explicit_merge_files(Dirname) -> MergeListFile = filename:join(Dirname, "merge.txt"), case read_lines(MergeListFile) of {error, ReadErr} -> case filelib:is_regular(MergeListFile) of true -> error_logger:error_msg("Invalid merge input file ~s," " deleting : ~p", [MergeListFile, ReadErr]), _ = file:delete(MergeListFile), []; false -> [] end; {ok, MergeList} -> case next_merge_batch(Dirname, MergeList) of [] -> _ = file:delete(MergeListFile), []; MergeBatch -> MergeBatch end end. -spec next_merge_batch(Dir::string(), Files::[binary()]) -> [string()]. next_merge_batch(Dir, Files) -> AllFiles = sets:from_list(readable_files(Dir)), Batch = lists:foldl(fun(<<>>, []=Acc) -> % Drop leading empty lines Acc; (<<>>, [_|_]=Acc) -> % Stop at first empty line after some files {batch, Acc}; (F0, Acc) when is_list(Acc) -> % Collect existing files F = filename:join(Dir, binary_to_list(F0)), case sets:is_element(F, AllFiles) of true -> [F|Acc]; false -> Acc end; (_, {batch, _}=Acc) -> % Ignore the rest Acc end, [], Files), BatchFiles = case Batch of {batch, List} -> List; List -> List end, lists:reverse(BatchFiles). -spec read_lines(string()) -> {ok, [binary()]} | {error, _}. read_lines(Filename) -> case file:read_file(Filename) of {ok, Bin} -> LineSeps = [<<"\n">>, <<"\r">>, <<"\r\n">>], {ok, binary:split(Bin, LineSeps, [global])}; Err -> Err end. run_merge_triggers(State, Summary) -> %% Triggers that would require a merge: %% %% frag_merge_trigger - Any file exceeds this % fragmentation %% dead_bytes_merge_trigger - Any file has more than this # of dead bytes %% expiry_time - Any file has an expired key %% expiry_grace_time - avoid expiring in the case of continuous writes %% FragTrigger = get_opt(frag_merge_trigger, State#bc_state.opts), DeadBytesTrigger = get_opt(dead_bytes_merge_trigger, State#bc_state.opts), ExpirationTime = max(expiry_time(State#bc_state.opts), 0), ExpirationGraceTime = max(expiry_time(State#bc_state.opts) - expiry_grace_time(State#bc_state.opts), 0), NeedsMerge = lists:any(consider_for_merge(FragTrigger, DeadBytesTrigger, ExpirationGraceTime), Summary), case NeedsMerge of true -> %% Build a list of threshold checks; a file which meets ANY %% of these will be merged %% %% frag_threshold - At least this % fragmented %% dead_bytes_threshold - At least this # of dead bytes %% small_file_threshold - Any file < this # of bytes %% expiry_secs - Any file has a expired key %% Thresholds = [frag_threshold(State#bc_state.opts), dead_bytes_threshold(State#bc_state.opts), small_file_threshold(State#bc_state.opts), expired_threshold(ExpirationTime)], %% For each file, apply the threshold checks and return a list %% of failed threshold checks CheckFile = fun(F) -> {F#file_status.filename, lists:flatten([T(F) || T <- Thresholds])} end, MergableFiles = [{N, R} || {N, R} <- [CheckFile(F) || F <- Summary], R /= []], %% Log the reasons for needing a merge, if so configured %% TODO: At some point we may want to change this API to let the caller %% recv this information and decide if they want it case get_opt(log_needs_merge, State#bc_state.opts) of true -> error_logger:info_msg("~p needs_merge: ~p\n", [State#bc_state.dirname, MergableFiles]); _ -> ok end, FileNames = [Filename || {Filename, _Reasons} <- MergableFiles], F = fun(X) -> case X of {data_expired,_,_} -> true; _ -> false end end, ExpiredFiles = [Filename || {Filename, Reasons} <- MergableFiles, lists:any(F,Reasons)], {true, {FileNames, ExpiredFiles}}; false -> false end. frag_threshold(Opts) -> FragThreshold = get_opt(frag_threshold, Opts), fun(F) -> if F#file_status.fragmented >= FragThreshold -> [{fragmented, F#file_status.fragmented}]; true -> [] end end. dead_bytes_threshold(Opts) -> DeadBytesThreshold = get_opt(dead_bytes_threshold, Opts), fun(F) -> if F#file_status.dead_bytes >= DeadBytesThreshold -> [{dead_bytes, F#file_status.dead_bytes}]; true -> [] end end. small_file_threshold(Opts) -> %% We need to do a special check on small_file_threshold for non-integer %% values since it is using a less-than check. Other thresholds typically %% do a greater-than check and can take advantage of fact that integers %% are always greater than an atom. case get_opt(small_file_threshold, Opts) of Threshold when is_integer(Threshold) -> fun(F) -> if F#file_status.total_bytes < Threshold -> [{small_file, F#file_status.total_bytes}]; true -> [] end end; disabled -> fun(_F) -> [] end end. expired_threshold(Cutoff) -> fun(F) -> if F#file_status.newest_tstamp < Cutoff -> [{data_expired, F#file_status.newest_tstamp, Cutoff}]; true -> [] end end. -spec is_empty_estimate(reference()) -> boolean(). is_empty_estimate(Ref) -> State = get_state(Ref), {KeyCount, _, _, _, _} = bitcask_nifs:keydir_info(State#bc_state.keydir), KeyCount == 0. -spec is_frozen(reference()) -> boolean(). is_frozen(Ref) -> #bc_state{keydir=Keydir} = get_state(Ref), {_, _, _, {_, _, Frozen, _}, _} = bitcask_nifs:keydir_info(Keydir), Frozen. -spec status(reference()) -> {integer(), [{string(), integer(), integer(), integer()}]}. status(Ref) -> %% Rewrite the new, record-style status from status_info into a backwards-compatible %% call. %% TODO: Next major revision should remove this variation on status {KeyCount, Summary} = summary_info(Ref), {KeyCount, [{F#file_status.filename, F#file_status.fragmented, F#file_status.dead_bytes, F#file_status.total_bytes} || F <- Summary]}. current_files(Dirname, Keydir) -> {_, _, Fstats, {_, _, _, PendingEpoch}, Epoch} = bitcask_nifs:keydir_info(Keydir), CappedEpoch = min(PendingEpoch, Epoch), FStatus = [summarize(Dirname, F) || F <- Fstats], CurrentFiles = [F || F <- FStatus, CappedEpoch < F#file_status.expiration_epoch], {CappedEpoch, CurrentFiles}. -spec summary_info(reference()) -> {integer(), [#file_status{}]}. summary_info(Ref) -> State = get_state(Ref), %% Pull current info for the bitcask. In particular, we want %% the file stats so we can determine how much fragmentation %% is present %% %% Fstat has form: [{FileId, LiveCount, TotalCount, LiveBytes, TotalBytes, %% OldestTstamp, NewestTstamp, ExpirationEpoch}] %% and is only an estimate/snapshot. {KeyCount, _KeyBytes, Fstats, _IterStatus, _Epoch} = bitcask_nifs:keydir_info(State#bc_state.keydir), %% We want to ignore the file currently being written when %% considering status! case bitcask_lockops:read_activefile(write, State#bc_state.dirname) of undefined -> WritingFileId = undefined; Filename -> WritingFileId = bitcask_fileops:file_tstamp(Filename) end, %% Convert fstats list into a list of #file_status %% %% Note that we also, filter the WritingFileId from any further %% consideration. Summary0 = [summarize(State#bc_state.dirname, S) || S <- Fstats, element(1, S) /= WritingFileId], %% Remove any files that don't exist from the initial summary Summary = lists:keysort(1, [S || S <- Summary0, bitcask_fileops:is_file(element(2, S))]), {KeyCount, Summary}. %% =================================================================== %% Internal functions %% =================================================================== summarize(Dirname, {FileId, LiveCount, TotalCount, LiveBytes, TotalBytes, OldestTstamp, NewestTstamp, ExpirationEpoch}) -> LiveRatio = case TotalCount > 0 of true -> LiveCount / TotalCount; false -> 0 end, #file_status { filename = bitcask_fileops:mk_filename(Dirname, FileId), fragmented = trunc((1 - LiveRatio) * 100), dead_bytes = TotalBytes - LiveBytes, total_bytes = TotalBytes, oldest_tstamp = OldestTstamp, newest_tstamp = NewestTstamp, expiration_epoch = ExpirationEpoch }. expiry_time(Opts) -> ExpirySecs = get_opt(expiry_secs, Opts), case ExpirySecs > 0 of true -> bitcask_time:tstamp() - ExpirySecs; false -> 0 end. to_lower_grace_time_bound(undefined) -> 0; to_lower_grace_time_bound(X) -> case X > 0 of true -> X; false -> 0 end. expiry_grace_time(Opts) -> to_lower_grace_time_bound(get_opt(expiry_grace_time, Opts)). is_tombstone(<>) -> true; is_tombstone(_) -> false. tombstone_context(<>) -> undefined; tombstone_context(<>) -> {before, FileId}; tombstone_context(<>) -> {at, FileId}; tombstone_context(_) -> no_tombstone. -spec tombstone_size_for_version(0|1|2) -> non_neg_integer(). tombstone_size_for_version(0) -> ?TOMBSTONE0_SIZE; tombstone_size_for_version(2) -> ?TOMBSTONE2_SIZE. -ifdef(TEST). start_app() -> catch application:start(?MODULE), ok. -else. start_app() -> case application:start(?MODULE) of ok -> ok; {error, {already_started, ?MODULE}} -> ok; {error, Reason} -> {error, Reason} end. -endif. get_state(Ref) -> case erlang:get(Ref) of S when is_record(S, bc_state) -> S; undefined -> throw({error, {invalid_ref, Ref}}) end. get_opt(Key, Opts) -> case proplists:get_value(Key, Opts) of undefined -> case application:get_env(?MODULE, Key) of {ok, Value} -> Value; undefined -> undefined end; Value -> Value end. put_state(Ref, State) -> erlang:put(Ref, State). kt_id(Key) when is_binary(Key) -> Key. scan_key_files([], _KeyDir, Acc, _CloseFile, _KT) -> Acc; scan_key_files([Filename | Rest], KeyDir, Acc, CloseFile, KT) -> %% Restrictive pattern matching below is intentional case bitcask_fileops:open_file(Filename) of {ok, File} -> FileTstamp = bitcask_fileops:file_tstamp(File), %% Signal to the keydir that this file exists via %% increment_file_id() with optional 2nd arg. The NIF %% needs to know the file exists, even if it contains only %% tombstones or data errors. Otherwise we risk of %% reusing the file id for new data. _ = bitcask_nifs:increment_file_id(KeyDir, FileTstamp), F = fun({tombstone, K0}, _Tstamp, {_Offset, _TotalSz}, _) -> K = try KT(K0) catch TxErr -> {key_tx_error, TxErr} end, case K of {key_tx_error, KeyTxErr} -> error_logger:error_msg("Invalid key on load ~p: ~p", [K0, KeyTxErr]), ok; _ -> bitcask_nifs:keydir_remove(KeyDir, KT(K)) end, ok; (K0, Tstamp, {Offset, TotalSz}, _) -> K = try KT(K0) catch TxErr -> {key_tx_error, TxErr} end, case K of {key_tx_error, KeyTxErr} -> error_logger:error_msg("Invalid key on load ~p: ~p", [K0, KeyTxErr]); _ -> bitcask_nifs:keydir_put(KeyDir, K, FileTstamp, TotalSz, Offset, Tstamp, bitcask_time:tstamp(), false) end, ok end, bitcask_fileops:fold_keys(File, F, undefined, recovery), if CloseFile == true -> bitcask_fileops:close(File); true -> ok end, scan_key_files(Rest, KeyDir, [File | Acc], CloseFile, KT) end. %% %% Initialize a keydir for a given directory. %% init_keydir(Dirname, WaitTime, ReadWriteModeP, KT) -> %% Get the named keydir for this directory. If we get it and it's already %% marked as ready, that indicates another caller has already loaded %% all the data from disk and we can short-circuit scanning all the files. case bitcask_nifs:keydir_new(Dirname) of {ready, KeyDir} -> %% A keydir already exists, nothing more to do here. We'll lazy %% open files as needed. {ok, KeyDir, []}; {not_ready, KeyDir} -> %% We've just created a new named keydir, so we need to load up all %% the data from disk. Build a list of all the bitcask data files %% and sort it in ascending order (oldest->newest). %% %% We need the SortedFiles list to be stable: we might be %% in a situation: %% 1. Someone else starts a merge on this cask. %% 2. Our caller opens the cask and gets to here. %% 3. The merge races with readable_files(): creating %% new data files and deleting old ones. %% 4. SortedFiles doesn't contain the list of all of the %% files that we need. Lock = poll_for_merge_lock(Dirname), ScanResult = try if ReadWriteModeP -> %% This purge will acquire the write lock %% prior to doing anything. purge_setuid_files(Dirname); true -> ok end, init_keydir_scan_key_files(Dirname, KeyDir, KT) catch _:Detail -> {error, {purge_setuid_or_init_scan, Detail}} after _ = case Lock of ?POLL_FOR_MERGE_LOCK_PSEUDOFAILURE -> ok; {error, _} = Error -> Error; _ -> ok = bitcask_lockops:release(Lock) end end, case ScanResult of {error, _} -> ok = bitcask_nifs:keydir_release(KeyDir), ScanResult; _ -> %% Now that we loaded all the data, mark the keydir as ready %% so other callers can use it ok = bitcask_nifs:keydir_mark_ready(KeyDir), {ok, KeyDir, []} end; {error, not_ready} -> timer:sleep(100), case WaitTime of Value when is_integer(Value), Value =< 0 -> %% avoids 'infinity'! {error, timeout}; _ -> init_keydir(Dirname, WaitTime - 100, ReadWriteModeP, KT) end end. init_keydir_scan_key_files(Dirname, KeyDir, KT) -> init_keydir_scan_key_files(Dirname, KeyDir, KT, ?DIABOLIC_BIG_INT). init_keydir_scan_key_files(_Dirname, _Keydir, _KT, 0) -> %% If someone launches enough parallel merge operations to %% interfere with our attempts to scan this keydir for this many %% times, then we are just plain unlucky. Or QuickCheck smites us %% from lofty Mt. Stochastic. {error, {init_keydir_scan_key_files, too_many_iterations}}; init_keydir_scan_key_files(Dirname, KeyDir, KT, Count) -> try {SortedFiles, SetuidFiles} = readable_and_setuid_files(Dirname), _ = scan_key_files(SortedFiles, KeyDir, [], true, KT), %% There may be a setuid data file that has a larger tstamp name than %% any non-setuid data file. Tell the keydir about it, so that we %% don't try to reuse that tstamp name. case SetuidFiles of [] -> ok; _ -> MaxSetuid = lists:max([bitcask_fileops:file_tstamp(F) || F <- SetuidFiles]), bitcask_nifs:increment_file_id(KeyDir, MaxSetuid) end catch ?_exception_(_X, _Y, StackToken) -> error_msg_perhaps("scan_key_files: ~p ~p @ ~p\n", [_X, _Y, ?_get_stacktrace_(StackToken)]), init_keydir_scan_key_files(Dirname, KeyDir, KT, Count - 1) end. get_filestate(FileId, State=#bc_state{ dirname = Dirname, read_files = ReadFiles }) -> case get_filestate(FileId, Dirname, ReadFiles, readonly) of {error, _} = Err -> Err; {Filestate, NewFiles} -> {Filestate, State#bc_state{read_files = NewFiles}} end; get_filestate(FileId, State = #mstate{ dirname = Dirname, tombstone_write_files = TFiles }) -> case get_filestate(FileId, Dirname, TFiles, append) of {error, _} = Err -> Err; {Filestate, NewFiles} -> {Filestate, State#mstate{tombstone_write_files = NewFiles}} end. get_filestate(FileId, Dirname, ReadFiles, Mode) -> case lists:keysearch(FileId, #filestate.tstamp, ReadFiles) of {value, Filestate} -> {Filestate, ReadFiles}; false -> Fname = bitcask_fileops:mk_filename(Dirname, FileId), case bitcask_fileops:open_file(Fname, Mode) of {error,enoent} -> %% merge removed the file since the keydir_get {error, enoent}; {error, Reason} -> {error, Reason}; {ok, Filestate} -> {Filestate, [Filestate | ReadFiles]} end end. list_data_files(Dirname, WritingFile, MergingFile) -> Files1 = bitcask_fileops:data_file_tstamps(Dirname), [F || {_Tstamp, F} <- lists:sort(Files1), F /= WritingFile, F /= MergingFile]. merge_files(#mstate { input_files = [] } = State) -> State; merge_files(#mstate { dirname = Dirname, input_files = [File | Rest], key_transform = KT } = State) -> FileId = bitcask_fileops:file_tstamp(File), F = fun(K0, V, Tstamp, Pos, State0) -> K = try KT(K0) catch Err -> {key_tx_error, Err} end, case K of {key_tx_error, TxErr} -> error_logger:error_msg("Invalid key on merge ~p: ~p", [K0, TxErr]), State0; _ -> merge_single_entry(K, V, Tstamp, FileId, Pos, State0) end end, State2 = try bitcask_fileops:fold(File, F, State) of #mstate{delete_files = DelFiles} = State1 -> State1#mstate{delete_files = [File|DelFiles]} catch throw:{fold_error, Error, _PartialAcc} -> error_logger:error_msg( "merge_files: skipping file ~s in ~s: ~p\n", [File#filestate.filename, Dirname, Error]), State end, merge_files(State2#mstate { input_files = Rest }). merge_single_entry(K, V, Tstamp, FileId, {_, _, Offset, _} = Pos, State) -> case out_of_date(State, K, Tstamp, FileId, Pos, State#mstate.expiry_time, false, [State#mstate.live_keydir, State#mstate.del_keydir]) of true -> %% Value in keydir is newer, so drop ... except! ... %% We aren't done yet: V might be a tombstone, which means %% that we might have to merge it forward. The func below %% is safe (does nothing) if V is not really a tombstone. merge_single_tombstone(K,V, Tstamp, FileId, Offset, State); expired -> %% Note: we drop a tombstone if it expired. Under normal %% circumstances it's OK as any value older than that has expired %% too and you wouldn't see values coming back to life after a %% merge and reopen. However with a clock going back in time, %% and badly timed quick merges you could end up seeing an old %% value after we drop a tombstone that has a lower timestamp %% than a value that was actually written before. Likely that other %% value would expire soon too, but... %% Remove only if this is the current entry in the keydir bitcask_nifs:keydir_remove(State#mstate.live_keydir, K, Tstamp, FileId, Offset), State; not_found -> %% First tombstone seen for this key during this merge merge_single_tombstone(K,V, Tstamp, FileId, Offset, State); false -> % Either a current value or a tombstone with nothing in the keydir % but an entry in the del keydir because we've seen another during % this merge. case is_tombstone(V) of true -> %% We have seen a tombstone for this key before, but this %% one is newer than that one. ok = bitcask_nifs:keydir_put(State#mstate.del_keydir, K, FileId, 0, Offset, Tstamp, bitcask_time:tstamp()), case State#mstate.merge_coverage of partial -> inner_merge_write(K, V, Tstamp, FileId, Offset, State); _ -> % Full or prefix merge, safe to drop the tombstone State end; false -> ok = bitcask_nifs:keydir_remove(State#mstate.del_keydir, K), inner_merge_write(K, V, Tstamp, FileId, Offset, State) end end. merge_single_tombstone(K,V, Tstamp, FileId, Offset, State) -> case tombstone_context(V) of undefined -> %% Version 1 tombstone, no info on deleted value %% Not in keydir and not already deleted. %% Remember we deleted this already during this merge. ok = bitcask_nifs:keydir_put(State#mstate.del_keydir, K, FileId, 0, Offset, Tstamp, bitcask_time:tstamp()), case State#mstate.merge_coverage of partial -> V2 = <>, %% Merging only some files, forward tombstone inner_merge_write(K, V2, Tstamp, FileId, Offset, State); _ -> %% Full or prefix merge, so safe to drop tombstone State end; {before, OldFileId} -> case State#mstate.min_file_id > OldFileId of true -> State; false -> inner_merge_write(K, V, Tstamp, FileId, Offset, State) end; {at, OldFileId} -> %% Tombstone has info on deleted value case sets:is_element(OldFileId, State#mstate.input_file_ids) of true -> %% Merge will waste the original value too. Drop it. State; false -> %% Append to original file case get_filestate(OldFileId, State) of {error, enoent} -> %% Original file is gone, safe to drop State; {TFile, State2 = #mstate{tombstone_write_files=TFiles}} -> %% Original file still around, append to it {ok, TFile2, _, TSize} = bitcask_fileops:write(TFile, K, V, Tstamp), ok = bitcask_nifs:update_fstats( State#mstate.live_keydir, OldFileId, Tstamp, _LiveKeys = 0, _TotalKeysIncr = 0, _LiveIncr = 0, _TotalIncr = TSize, _ShouldCreate = 0), TFiles2 = lists:keyreplace( TFile#filestate.filename, #filestate.filename, TFiles, TFile2), State2#mstate{tombstone_write_files=TFiles2} end end; no_tombstone -> %% Regular value not currently in keydir, ignore State end. -spec inner_merge_write(binary(), binary(), integer(), integer(), integer(), #mstate{}) -> #mstate{}. inner_merge_write(K, V, Tstamp, OldFileId, OldOffset, State) -> %% write a single item while inside the merge process %% See if it's time to rotate to the next file State1 = case bitcask_fileops:check_write(State#mstate.out_file, K, size(V), State#mstate.max_file_size) of wrap -> %% Close the current output file ok = bitcask_fileops:sync(State#mstate.out_file), ok = bitcask_fileops:close(State#mstate.out_file), %% Start our next file and update state {ok, NewFile} = bitcask_fileops:create_file( State#mstate.dirname, State#mstate.opts, State#mstate.live_keydir), NewFileName = bitcask_fileops:filename(NewFile), ok = bitcask_lockops:write_activefile( State#mstate.merge_lock, NewFileName), State#mstate { out_file = NewFile }; ok -> State; fresh -> %% create the output file and take the lock. {ok, NewFile} = bitcask_fileops:create_file( State#mstate.dirname, State#mstate.opts, State#mstate.live_keydir), NewFileName = bitcask_fileops:filename(NewFile), ok = bitcask_lockops:write_activefile( State#mstate.merge_lock, NewFileName), State#mstate { out_file = NewFile } end, {ok, Outfile, Offset, Size} = bitcask_fileops:write(State1#mstate.out_file, K, V, Tstamp), OutFileId = bitcask_fileops:file_tstamp(Outfile), case OutFileId =< OldFileId of true -> exit({invariant_violation, K, V, OldFileId, OldOffset, "->", OutFileId, Offset}); false -> ok end, Outfile2 = case is_tombstone(V) of false -> %% Update live keydir for the current out %% file. It's possible that someone else may have written %% a newer value whilst we were processing ... and if %% they did, we need to undo our write here. case bitcask_nifs:keydir_put(State1#mstate.live_keydir, K, OutFileId, Size, Offset, Tstamp, bitcask_time:tstamp(), OldFileId, OldOffset) of ok -> Outfile; already_exists -> {ok, O} = bitcask_fileops:un_write(Outfile), O end; true -> case bitcask_nifs:keydir_get(State1#mstate.live_keydir, K) of not_found -> % Update timestamp and total bytes stats ok = bitcask_nifs:update_fstats( State1#mstate.live_keydir, OutFileId, Tstamp, 0, 0, 0, Size, _ShouldCreate = 1), % Still not there, tombstone write is cool Outfile; #bitcask_entry{} -> % New value written, undo {ok, O} = bitcask_fileops:un_write(Outfile), O end end, State1#mstate { out_file = Outfile2 }. out_of_date(_State, _Key, _Tstamp, _FileId, _Pos, _ExpiryTime, EverFound, []) -> %% if we ever found it, and none of the entries were out of date, %% then it's not out of date case EverFound of true -> false; false -> not_found end; out_of_date(_State, _Key, Tstamp, _FileId, _Pos, ExpiryTime, _EverFound, _KeyDirs) when Tstamp < ExpiryTime -> expired; out_of_date(State, Key, Tstamp, FileId, {_,_,Offset,_} = Pos, ExpiryTime, EverFound, [KeyDir|Rest]) -> case bitcask_nifs:keydir_get(KeyDir, Key) of not_found -> out_of_date(State, Key, Tstamp, FileId, Pos, ExpiryTime, EverFound, Rest); E when is_record(E, bitcask_entry) -> if E#bitcask_entry.tstamp == Tstamp -> %% Exact match. In this situation, we use the file %% id and offset as a tie breaker. %% The assumption is that newer values are written to %% higher file ids and offsets, even in the case of a merge %% racing with the write process, as the writer process %% will detect that and retry the write to an even higher %% file id. if E#bitcask_entry.file_id > FileId -> true; E#bitcask_entry.file_id == FileId -> case E#bitcask_entry.offset > Offset of true -> true; false -> out_of_date( State, Key, Tstamp, FileId, Pos, ExpiryTime, true, Rest) end; true -> %% At this point the following conditions are true: %% The file_id in the keydir is older (<) the file %% id we're currently merging... %% %% OR: %% %% The file_id in the keydir is the same (==) as the %% file we're merging BUT the offset the keydir has %% is older (<=) the offset we are currently %% processing. %% %% Thus, we are NOT out of date. Check the %% rest of the keydirs to ensure this %% holds true. out_of_date(State, Key, Tstamp, FileId, Pos, ExpiryTime, true, Rest) end; E#bitcask_entry.tstamp < Tstamp -> %% Not out of date -- check rest of the keydirs out_of_date(State, Key, Tstamp, FileId, Pos, ExpiryTime, true, Rest); true -> %% Out of date! true end end. -spec readable_files(string()) -> [string()]. readable_files(Dirname) -> {ReadableFiles, _SetuidFiles} = readable_and_setuid_files(Dirname), ReadableFiles. readable_and_setuid_files(Dirname) -> %% Check the write and/or merge locks to see what files are currently %% being written to. Generate our list excepting those. WritingFile = bitcask_lockops:read_activefile(write, Dirname), MergingFile = bitcask_lockops:read_activefile(merge, Dirname), %% Filter out files with setuid bit set: they've been marked for %% deletion by an earlier *successful* merge. Fs = [F || F <- list_data_files(Dirname, WritingFile, MergingFile)], WritingFile2 = bitcask_lockops:read_activefile(write, Dirname), MergingFile2 = bitcask_lockops:read_activefile(merge, Dirname), case {WritingFile2, MergingFile2} of {WritingFile, MergingFile} -> lists:partition(fun(F) -> not has_pending_delete_bit(F) end, Fs); _ -> % Changed while fetching file list, retry readable_and_setuid_files(Dirname) end. %% Internal put - have validated that the file is opened for write %% and looked up the state at this point do_put(_Key, _Value, State, 0, LastErr) -> {{error, LastErr}, State}; do_put(Key, Value, #bc_state{write_file = WriteFile} = State, Retries, _LastErr) -> ValSize = case Value of tombstone -> tombstone_size_for_version(State#bc_state.tombstone_version); _ -> size(Value) end, State2 = case bitcask_fileops:check_write(WriteFile, Key, ValSize, State#bc_state.max_file_size) of wrap -> %% Time to start a new write file. Note that we do not %% close the old one, just transition it. The thinking %% is that closing/reopening for read only access %% would flush the O/S cache for the file, which may %% be undesirable. wrap_write_file(State); fresh -> %% Time to start our first write file. case bitcask_lockops:acquire(write, State#bc_state.dirname) of {ok, WriteLock} -> try {ok, NewWriteFile} = bitcask_fileops:create_file( State#bc_state.dirname, State#bc_state.opts, State#bc_state.keydir), ok = bitcask_lockops:write_activefile( WriteLock, bitcask_fileops:filename(NewWriteFile)), State#bc_state{ write_file = NewWriteFile, write_lock = WriteLock } catch error:{badmatch,Error} -> throw({unrecoverable, Error, State}) end; {error, _} = Error -> throw({unrecoverable, Error, State}) end; ok -> State end, Tstamp = bitcask_time:tstamp(), #bc_state{write_file=WriteFile0} = State2, WriteFileId = bitcask_fileops:file_tstamp(WriteFile0), case Value of BinValue when is_binary(BinValue) -> % Replacing value from a previous file, so write tombstone for it. case bitcask_nifs:keydir_get(State2#bc_state.keydir, Key) of #bitcask_entry{file_id=OldFileId} when OldFileId > WriteFileId -> State3 = wrap_write_file(State2), do_put(Key, Value, State3, Retries - 1, already_exists); #bitcask_entry{file_id=OldFileId,offset=OldOffset} -> State3 = case OldFileId < WriteFileId of true -> PrevTomb = <>, {ok, WriteFile1, _, _} = bitcask_fileops:write(WriteFile0, Key, PrevTomb, Tstamp), State2#bc_state{write_file = WriteFile1}; false -> State2 end, write_and_keydir_put(State3, Key, Value, Tstamp, Retries, bitcask_time:tstamp(), OldFileId, OldOffset); _ -> State3 = State2#bc_state{write_file = WriteFile0}, write_and_keydir_put(State3, Key, Value, Tstamp, Retries, bitcask_time:tstamp(), 0, 0) end; tombstone -> case bitcask_nifs:keydir_get(State2#bc_state.keydir, Key) of not_found -> {ok, State2}; #bitcask_entry{file_id=OldFileId} when OldFileId > WriteFileId -> % A merge wrote this key in a file > current write file % Start a new write file > the merge output file State3 = wrap_write_file(State2), do_put(Key, Value, State3, Retries - 1, already_exists); #bitcask_entry{tstamp=OldTstamp, file_id=OldFileId, offset=OldOffset} -> Tombstone = <>, case bitcask_fileops:write(State2#bc_state.write_file, Key, Tombstone, Tstamp) of {ok, WriteFile2, _, TSize} -> ok = bitcask_nifs:update_fstats( State2#bc_state.keydir, bitcask_fileops:file_tstamp(WriteFile2), Tstamp, 0, 0, 0, TSize, _ShouldCreate = 1), case bitcask_nifs:keydir_remove(State2#bc_state.keydir, Key, OldTstamp, OldFileId, OldOffset) of already_exists -> %% Merge updated the keydir after tombstone %% write. beat us, so undo and retry in a %% new file. {ok, WriteFile3} = bitcask_fileops:un_write(WriteFile2), State3 = wrap_write_file( State2#bc_state { write_file = WriteFile3 }), do_put(Key, Value, State3, Retries - 1, already_exists); ok -> {ok, State2#bc_state { write_file = WriteFile2 }} end; {error, _} = ErrorTomb -> throw({unrecoverable, ErrorTomb, State2}) end end end. write_and_keydir_put(State2, Key, Value, Tstamp, Retries, NowTstamp, OldFileId, OldOffset) -> case bitcask_fileops:write(State2#bc_state.write_file, Key, Value, Tstamp) of {ok, WriteFile2, Offset, Size} -> case bitcask_nifs:keydir_put(State2#bc_state.keydir, Key, bitcask_fileops:file_tstamp(WriteFile2), Size, Offset, Tstamp, NowTstamp, true, OldFileId, OldOffset) of ok -> {ok, State2#bc_state { write_file = WriteFile2 }}; already_exists -> %% Assuming the timestamps in the keydir are %% valid, there is an edge case where the merge thread %% could have rewritten this Key to a file with a greater %% file_id. Rather than synchronize the merge/writer processes, %% wrap to a new file with a greater file_id and rewrite %% the key there. %% We must undo the write here so a later partial merge %% does not see it. %% Limit the number of recursions in case there is %% a different issue with the keydir. {ok, WriteFile3} = bitcask_fileops:un_write(WriteFile2), State3 = wrap_write_file( State2#bc_state { write_file = WriteFile3 }), do_put(Key, Value, State3, Retries - 1, already_exists) end; Error2 -> throw({unrecoverable, Error2, State2}) end. wrap_write_file(#bc_state{write_file = WriteFile} = State) -> try LastWriteFile = bitcask_fileops:close_for_writing(WriteFile), {ok, NewWriteFile} = bitcask_fileops:create_file( State#bc_state.dirname, State#bc_state.opts, State#bc_state.keydir), ok = bitcask_lockops:write_activefile( State#bc_state.write_lock, bitcask_fileops:filename(NewWriteFile)), State#bc_state{ write_file = NewWriteFile, read_files = [LastWriteFile | State#bc_state.read_files]} catch error:{badmatch,Error} -> throw({unrecoverable, Error, State}) end. %% Versions of Bitcask prior to %% https://github.com/basho/bitcask/pull/156 used the setuid bit to %% indicate that the data file has been deleted logically and is %% waiting for a physical delete from the 'bitcask_merge_delete' server. %% %% However, with PR 156, we change the lifecycle of a .data file: it's %% possible to append tombstones during a merge to a file that is %% pending deletion. If that happens, the file system will clear the %% setuid bit when the first append happens. That's not good. %% %% Going forward, instead of using the setuid bit for pending delete %% status, we'll use the 8#001 execution permission bit. %% For backward compatibility, has_pending_delete_bit() will check for %% both the old pending bit, 8#40000 (setuid), as well as 8#0001. set_pending_delete_bit(File) -> %% We're intentionally opinionated about pattern matching here. {ok, FI} = bitcask_fileops:read_file_info(File), NewFI = FI#file_info{mode = FI#file_info.mode bor 8#0001}, ok = bitcask_fileops:write_file_info(File, NewFI). has_pending_delete_bit(File) -> try {ok, FI} = bitcask_fileops:read_file_info(File), FI#file_info.mode band 8#4001 /= 0 catch _:_ -> false end. purge_setuid_files(Dirname) -> case bitcask_lockops:acquire(write, Dirname) of {ok, WriteLock} -> try StaleFs = [F || F <- list_data_files(Dirname, undefined, undefined), has_pending_delete_bit(F)], _ = [bitcask_fileops:delete(F) || F <- StaleFs], if StaleFs == [] -> ok; true -> error_logger:info_msg("Deleted ~p stale merge input " "files from ~p\n", [length(StaleFs), Dirname]) end catch ?_exception_(X, Y, StackToken) -> error_msg_perhaps("While deleting stale merge input " "files from ~p: ~p @ ~p\n", [X, Y, ?_get_stacktrace_(StackToken)]) after bitcask_lockops:release(WriteLock) end; Else -> error_logger:info_msg("Lock failed trying deleting stale merge " "input files from ~p: ~p\n", [Dirname, Else]) end. poll_for_merge_lock(Dirname) -> poll_for_merge_lock(Dirname, 20). poll_for_merge_lock(_Dirname, 0) -> case erlang:get(bitcask_testing_module) of undefined -> {error, {poll_for_merge_lock, not_testing, max_limit}}; _TestMod -> ?POLL_FOR_MERGE_LOCK_PSEUDOFAILURE end; poll_for_merge_lock(Dirname, N) -> case bitcask_lockops:acquire(merge, Dirname) of {ok, Lock} -> Lock; _ -> timer:sleep(100), poll_for_merge_lock(Dirname, N-1) end. %% Internal merge function for cache_merge functionality. expiry_merge([], _LiveKeyDir, _KT, Acc) -> Acc; expiry_merge([File | Files], LiveKeyDir, KT, Acc0) -> FileId = bitcask_fileops:file_tstamp(File), Fun = fun({tombstone, _}, _, _, Acc) -> Acc; (K0, Tstamp, {Offset, _TotalSz}, Acc) -> K = try KT(K0) catch TxErr -> {key_tx_error, TxErr} end, case K of {key_tx_error, KeyTxErr} -> error_logger:error_msg("Invalid key on merge ~p: ~p", [K0, KeyTxErr]); _ -> bitcask_nifs:keydir_remove(LiveKeyDir, K, Tstamp, FileId, Offset) end, Acc end, case bitcask_fileops:fold_keys(File, Fun, ok, default) of {error, Reason} -> error_logger:error_msg("Error folding keys for ~p: ~p\n", [File#filestate.filename,Reason]), Acc = Acc0; _ -> error_logger:info_msg("All keys expired in: ~p scheduling " "file for deletion\n", [File#filestate.filename]), Acc = lists:append(Acc0, [File]) end, expiry_merge(Files, LiveKeyDir, KT, Acc). get_key_transform(KT) when is_function(KT) -> KT; get_key_transform(_State) -> fun kt_id/1. -ifdef(TEST). error_msg_perhaps(_Fmt, _Args) -> ok. -else. %TEST error_msg_perhaps(Fmt, Args) -> error_logger:error_msg(Fmt, Args). -endif. %TEST %% =================================================================== %% EUnit tests %% =================================================================== -ifdef(TEST). init_dataset(Dirname, KVs) -> init_dataset(Dirname, [], KVs). init_dataset(Dirname, Opts, KVs) -> os:cmd(?FMT("rm -rf ~s", [Dirname])), B = bitcask:open(Dirname, [read_write] ++ Opts), put_kvs(B, KVs), B. put_kvs(B, KVs) -> lists:foldl(fun({K, V}, _) -> ok = bitcask:put(B, K, V) end, undefined, KVs). default_dataset() -> [{<<"k">>, <<"v">>}, {<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]. %% HACK: Terrible hack to ensure that the .app file for %% bitcask is available on the code path. Assumption here %% is that we're running in .eunit/ as part of rebar. a0_test_() -> {timeout, 60, fun a0_test2/0}. a0_test2() -> code:add_pathz("../ebin"), application:start(erlang), Mode = bitcask_io:determine_file_module(), error_logger:info_msg("Bitcask IO mode is: ~p\n", [Mode]). roundtrip_test_() -> {timeout, 60, fun roundtrip_test2/0}. setup_testfolder(Path) -> DN = filename:join(?TEST_FILEPATH, Path), os:cmd("rm -rf " ++ DN), DN. roundtrip_test2() -> FN = setup_testfolder("bc.test.roundtrip"), B = bitcask:open(FN, [read_write]), ok = bitcask:put(B,<<"k">>,<<"v">>), {ok, <<"v">>} = bitcask:get(B,<<"k">>), ok = bitcask:put(B, <<"k2">>, <<"v2">>), ok = bitcask:put(B, <<"k">>,<<"v3">>), {ok, <<"v2">>} = bitcask:get(B, <<"k2">>), {ok, <<"v3">>} = bitcask:get(B, <<"k">>), close(B). write_lock_perms_test_() -> {timeout, 60, fun write_lock_perms_test2/0}. write_lock_perms_test2() -> FN = setup_testfolder("bc.test.writelockperms"), B = bitcask:open(FN, [read_write]), ok = bitcask:put(B, <<"k">>, <<"v">>), {ok, Info} = file:read_file_info(filename:join(FN, "bitcask.write.lock")), ?assertEqual(8#00600, Info#file_info.mode band 8#00600), ok = bitcask:close(B), os:cmd("rm -rf " ++ FN). list_data_files_test_() -> {timeout, 60, fun list_data_files_test2/0}. list_data_files_test2() -> DN = setup_testfolder("bc.test.list"), os:cmd("mkdir -p " ++ DN), %% Generate a list of files from 8->12 ExpFiles = [?FMT(DN ++ "/~w.bitcask.data", [I]) || I <- lists:seq(8, 12)], %% Create each of the files [] = os:cmd(?FMT("touch ~s", [string:join(ExpFiles, " ")])), %% Now use the list_data_files to scan the dir ExpFiles = list_data_files(DN, undefined, undefined). % Test that readable_files will not return the currently active % write or merge file by mistake if they change in between fetching them % and listing the files in the directory. list_data_files_race_test() -> Dir = setup_testfolder("bc.test.list.race"), Fname = fun(N) -> filename:join(Dir, integer_to_list(N) ++ ".bitcask.data") end, WriteFile = fun(N) -> ok = file:write_file(Fname(N), <<>>) end, WriteFiles = fun(S,E) -> [WriteFile(N) || N <- lists:seq(S, E)] end, os:cmd("rm -rf " ++ Dir ++ "; mkdir -p " ++ Dir), WriteFiles(1,5), % Faking 4 as merge file, 5 as write file, % then switching to 6 as merge, 7 as write KindN = fun(merge) -> 4; (write) -> 5 end, meck:new(bitcask_lockops, [passthrough]), meck:expect(bitcask_lockops, read_activefile, fun(Kind, _) -> case get({fake_activefile, Kind}) of undefined -> N = KindN(Kind), % Next time return file + 2 WriteFile(N+2), put({fake_activefile, Kind}, Fname(N+2)), Fname(N); File -> File end end), ReadFiles = lists:usort(bitcask:readable_files(Dir)), meck:unload(), ?assertEqual([Fname(N)||N<-lists:seq(1,5)], ReadFiles). fold_test_() -> {timeout, 60, fun fold_test2/0}. fold_test2() -> DN = setup_testfolder("bc.test.fold"), B = init_dataset(DN, default_dataset()), File = (get_state(B))#bc_state.write_file, L = bitcask_fileops:fold(File, fun(K, V, _Ts, _Pos, Acc) -> [{K, V} | Acc] end, []), ?assertEqual(default_dataset(), lists:reverse(L)), close(B). iterator_test_() -> {timeout, 60, fun iterator_test2/0}. iterator_test2() -> DN = setup_testfolder("bc.iterator.test.fold"), B = init_dataset(DN, default_dataset()), ok = iterator(B, 0, 0), Keys = [ begin #bitcask_entry{ key = Key } = iterator_next(B), Key end || _ <- default_dataset() ], ?assertEqual(lists:sort(Keys), lists:sort([ Key || {Key, _} <- default_dataset() ])), iterator_release(B). fold_corrupt_file_test_() -> {timeout, 60, fun fold_corrupt_file_test2/0}. fold_corrupt_file_test2() -> TestDir = setup_testfolder("bc.test.fold_corrupt_file_test"), DataList = [{<<"k">>, <<"v">>}], CreateFun = fun(KVList) -> os:cmd("rm -rf " ++ TestDir), B = bitcask:open(TestDir, [read_write]), [ begin ok = bitcask:put(B, K, V) end || {K, V} <- KVList], close(B), bitcask:readable_files(TestDir) end, [File1] = CreateFun(DataList), FoldFun = fun (K, V, Acc) -> [{K, V} | Acc] end, B2 = bitcask:open(TestDir), ?assertEqual(DataList, bitcask:fold(B2, FoldFun,[])), close(B2), % Incomplete header {ok, File1Before} = file:read_file(File1), {ok, F} = file:open(File1, [append, raw, binary]), ok = file:write(F, <<100:32, 100:32, 100:16>>), file:close(F), {ok, File1After} = file:read_file(File1), ?assert(File1Before /= File1After), B3 = bitcask:open(TestDir), ?assertEqual(DataList, bitcask:fold(B3, FoldFun,[])), close(B3), % Re-create before trying next corruption [File2] = CreateFun(DataList), {ok, File2Before} = file:read_file(File2), % Header without any data {ok, F2} = file:open(File2, [append, raw, binary]), ok = file:write(F2, <<100:32>>), file:close(F2), {ok, File2After} = file:read_file(File2), ?assert(File2Before /= File2After), B4 = bitcask:open(TestDir), ?assertEqual(DataList, bitcask:fold(B4, FoldFun,[])), close(B4), %% If record is corrupted, the key should not be loaded. DataList2 = [{<<"k1">>, <<"v1">>}, {<<"k2">>, <<"v2">>}], [File3] = CreateFun(DataList2), {ok, File3Before} = file:read_file(File3), Hintfile = bitcask_fileops:hintfile_name(File3), ?assertEqual(true, filelib:is_regular(File3)), ?assertEqual(true, filelib:is_regular(Hintfile)), ok = file:delete(Hintfile), ?assertEqual(false, filelib:is_regular(Hintfile)), %% Change last byte of value for k2 to invalidate its CRC. {ok, F3} = file:open(File3, [binary, read, write]), ok = file:pwrite(F3, {eof, -1}, <<"3">>), ok = file:close(F3), {ok, File3After} = file:read_file(File3), ?assert(File3Before /= File3After), B5 = bitcask:open(TestDir), LoadedKeys = bitcask:list_keys(B5), ?assertEqual([<<"k1">>], LoadedKeys), bitcask:close(B5), ok. % Issue puts until the entries hash in the keydir can not be updated anymore % and a pending hash is created. There *has* to be an iterator open when you % call this or it will loop for ever and ever. Don't try this at home. put_till_frozen(B) -> Key = crypto:strong_rand_bytes(32), bitcask:put(B, Key, <<>>), bitcask:delete(B, Key), case bitcask:is_frozen(B) of true -> ok; false -> put_till_frozen(B) end. %% %% Check that fold visits the objects at the point the keydir %% was frozen. Check with and without wrapping the cask. %% fold_visits_frozen_test_() -> [{timeout, 60, ?_test(fold_visits_frozen_test2(false))}, {timeout, 60, ?_test(fold_visits_frozen_test2(true))}]. fold_visits_frozen_test2(RollOver) -> Cask = setup_testfolder("bc.test.frozenfold" ++ atom_to_list(RollOver)), B = init_dataset(Cask, default_dataset()), try Ref = (get_state(B))#bc_state.keydir, Me = self(), %% Freeze the keydir with default_dataset written FrozenFun = fun() -> Me ! frozen, receive done -> ok end end, FreezeWaiter = proc_lib:spawn_link( fun() -> B2 = bitcask:open(Cask, [read]), Ref2 = (get_state(B2))#bc_state.keydir, %% Start a function that waits in a frozen keydir for a message %% so the test fold can guarantee a frozen keydir ok = bitcask_nifs:keydir_frozen(Ref2, FrozenFun, -1, -1), bitcask:close(B2) end), receive frozen -> ok after 1000 -> ?assert(keydir_not_frozen) end, %% make sure that it's actually frozen put_till_frozen(B), %% If checking file rollover, update the state so the next write will %% trigger a 'wrap' return. case RollOver of true -> State = get_state(B), put_state(B, State#bc_state{max_file_size = 0}); _ -> ok end, %% A delete, an update and an insert ok = delete(B, <<"k">>), ok = put(B, <<"k2">>, <<"v2-2">>), ok = put(B, <<"k4">>, <<"v4">>), timer:sleep(900), %% wait for the disk to settle CollectAll = fun(K, V, Acc) -> [{K, V} | Acc] end, %% force fold over the frozen keydir L = fold(B, CollectAll, [], -1, -1, false), ?assertEqual(default_dataset(), lists:sort(L)), %% Unfreeze the keydir, waiting until complete FreezeWaiter ! done, ok = bitcask_nifs:keydir_wait_pending(Ref), %% TODO: find some ironclad way of coordinating the disk and %% test state, instead of using sleeps. timer:sleep(900), %% Check we see the updated fold L2 = fold(B, CollectAll, [], -1, -1, false), ?assertEqual([{<<"k2">>,<<"v2-2">>}, {<<"k3">>,<<"v3">>}, {<<"k4">>,<<"v4">>}], lists:sort(L2)) after bitcask:close(B) end. fold_visits_unfrozen_test_() -> [{timeout, 60, ?_test(fold_visits_unfrozen_test2(false))}, {timeout, 60, ?_test(fold_visits_unfrozen_test2(true))}]. slow_worker() -> {Owner, Values} = receive {owner, O, Vs} -> {O, Vs} end, SlowCollect = fun(K, V, Acc) -> if Acc == [] -> Owner ! i_have_started_folding, receive go_ahead_with_fold -> ok end; true -> ok end, receive go -> ok end, [{K, V} | Acc] end, B = bitcask:open(filename:join(?TEST_FILEPATH, "bc.test.unfrozenfold")), Owner ! ready, L = fold(B, SlowCollect, [], -1, -1, false), case Values =:= lists:sort(L) of true -> Owner ! done; false -> Owner ! {sad, L} end, %%?debugFmt("slow worker finished~n", []), bitcask:close(B). finish_worker_loop(Pid) -> receive done -> done; {sad, L} -> {sad, L} after 0 -> Pid ! go, finish_worker_loop(Pid) end. fold_visits_unfrozen_test2(RollOver) -> %%?debugFmt("rollover is ~p~n", [RollOver]), Cask = setup_testfolder("bc.test.unfrozenfold"), bitcask_time:test__set_fudge(1), B = init_dataset(Cask, default_dataset()), try Pid = spawn(fun slow_worker/0), Pid ! {owner, self(), default_dataset()}, %% If checking file rollover, update the state so the next write will %% trigger a 'wrap' return. case RollOver of true -> State = get_state(B), put_state(B, State#bc_state{max_file_size = 0}); _ -> ok end, receive i_have_started_folding -> ok after 10*1000 -> error(timeout_should_never_happen) end, %% Until time independent epochs are merged, the fold timestamp %% is used to determine the snapshot, which has a 1 second resolution. %% So make sure updates happen at least 1 sec after folding starts bitcask_time:test__incr_fudge(1), %% A delete, an update and an insert ok = delete(B, <<"k">>), ok = put(B, <<"k2">>, <<"v2-2">>), ok = put(B, <<"k4">>, <<"v4">>), Pid ! go_ahead_with_fold, CollectAll = fun(K, V, Acc) -> [{K, V} | Acc] end, %% Unfreeze the keydir, waiting until complete case finish_worker_loop(Pid) of done -> ok; {sad, L} -> ?assertEqual(default_dataset(), lists:sort(L)) end, %% Check we see the updated fold L2 = fold(B, CollectAll, [], -1, -1, false), ?assertEqual([{<<"k2">>,<<"v2-2">>}, {<<"k3">>,<<"v3">>}, {<<"k4">>,<<"v4">>}], lists:sort(L2)) %%?debugFmt("got past the asserts??~n", []) after bitcask:close(B), bitcask_time:test__clear_fudge() end. open_test_() -> {timeout, 60, fun open_test2/0}. open_test2() -> DN = setup_testfolder("bc.test.open"), close(init_dataset(DN, default_dataset())), B = bitcask:open(DN), lists:foldl(fun({K, V}, _) -> {ok, V} = bitcask:get(B, K) end, undefined, default_dataset()), ok = bitcask:close(B). wrap_test_() -> {timeout, 60, fun wrap_test2/0}. wrap_test2() -> %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. DN = setup_testfolder("bc.test.wrap"), close(init_dataset(DN, [{max_file_size, 1}], default_dataset())), B = bitcask:open(DN), %% Check that all our data is available lists:foldl(fun({K, V}, _) -> {ok, V} = bitcask:get(B, K) end, undefined, default_dataset()), %% Finally, verify that there are 3 files currently opened for read %% (one for each key) 3 = length(readable_files(DN)), ok = bitcask:close(B). merge_test_() -> {timeout, 60, fun merge_test2/0}. merge_test2() -> %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. DN = setup_testfolder("bc.test.wrap"), close(init_dataset(DN, [{max_file_size, 1}], default_dataset())), timer:sleep(900), %% Verify number of files in directory 3 = length(readable_files(DN)), %% test that we can't merge a closed cask. {error, not_ready} = (catch merge(DN)), %% Merge everything M = bitcask:open(DN), ok = merge(DN), bitcask:close(M), %% Verify we've now only got one file 1 = length(readable_files(DN)), %% Make sure all the data is present B = bitcask:open(DN), lists:foldl(fun({K, V}, _) -> R = bitcask:get(B, K), ?assertEqual({K, {ok, V}}, {K, R}) end, undefined, default_dataset()), ok = bitcask:close(B). bitfold_test_() -> {timeout, 60, fun bitfold_test2/0}. bitfold_test2() -> DN = setup_testfolder("bc.test.bitfold"), B = bitcask:open(DN, [read_write]), ok = bitcask:put(B,<<"k">>,<<"v">>), {ok, <<"v">>} = bitcask:get(B,<<"k">>), ok = bitcask:put(B, <<"k2">>, <<"v2">>), ok = bitcask:put(B, <<"k">>,<<"v3">>), {ok, <<"v2">>} = bitcask:get(B, <<"k2">>), {ok, <<"v3">>} = bitcask:get(B, <<"k">>), ok = bitcask:delete(B,<<"k">>), ok = bitcask:put(B, <<"k7">>,<<"v7">>), close(B), B2 = bitcask:open(DN), true = ([{<<"k7">>,<<"v7">>},{<<"k2">>,<<"v2">>}] =:= bitcask:fold(B2,fun(K,V,Acc) -> [{K,V}|Acc] end,[])), close(B2), ok. fold1_test_() -> {timeout, 60, fun fold1_test2/0}. fold1_test2() -> DN = filename:join(?TEST_FILEPATH, "bc.test.fold1"), os:cmd("rm -rf " ++ DN), B = bitcask:open(DN, [read_write,{max_file_size, 1}]), ok = bitcask:put(B,<<"k">>,<<"v">>), ok = bitcask:put(B,<<"k">>,<<"v1">>), close(B), B2 = bitcask:open(DN), true = ([{<<"k">>,<<"v1">>}] =:= bitcask:fold(B2,fun(K,V,Acc) -> [{K,V}|Acc] end,[])), close(B2), ok. list_keys_test_() -> {timeout, 60, fun list_keys_test2/0}. list_keys_test2() -> DN = setup_testfolder("bc.test.listkeys"), B = bitcask:open(DN, [read_write]), ok = bitcask:put(B,<<"k">>,<<"v">>), {ok, <<"v">>} = bitcask:get(B,<<"k">>), ok = bitcask:put(B, <<"k2">>, <<"v2">>), ok = bitcask:put(B, <<"k">>,<<"v3">>), {ok, <<"v2">>} = bitcask:get(B, <<"k2">>), {ok, <<"v3">>} = bitcask:get(B, <<"k">>), ok = bitcask:delete(B,<<"k">>), ok = bitcask:put(B, <<"k7">>,<<"v7">>), Keys = bitcask:list_keys(B), close(B), ?assertEqual([<<"k2">>,<<"k7">>], lists:sort(Keys)), ok. expire_test_() -> {timeout, 60, fun expire_test2/0}. expire_test2() -> DN = setup_testfolder("bc.test.expire"), B = bitcask:open(DN, [read_write,{expiry_secs,1}]), ok = bitcask:put(B,<<"k">>,<<"v">>), {ok, <<"v">>} = bitcask:get(B,<<"k">>), ok = bitcask:put(B, <<"k2">>, <<"v2">>), ok = bitcask:put(B, <<"k">>,<<"v3">>), {ok, <<"v2">>} = bitcask:get(B, <<"k2">>), {ok, <<"v3">>} = bitcask:get(B, <<"k">>), timer:sleep(2000), ok = bitcask:put(B, <<"k7">>,<<"v7">>), true = ([<<"k7">>] =:= bitcask:list_keys(B)), close(B), ok. expire_merge_test_() -> {timeout, 60, fun expire_merge_test2/0}. expire_merge_test2() -> %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. DN = setup_testfolder("bc.test.mergeexpire"), close(init_dataset(DN, [{max_file_size, 1}], default_dataset())), %% Wait for it all to expire timer:sleep(2000), %% Merge everything M = bitcask:open(DN), ok = merge(DN,[{expiry_secs,1}]), bitcask:close(M), %% With lazy merge file creation there will be no files. ok = bitcask_merge_delete:testonly__delete_trigger(), 0 = length(readable_files(DN)), %% Make sure all the data is present B = bitcask:open(DN), %% It's gone! true = ([] =:= bitcask:list_keys(B)), close(B), ok. fold_deleted_test_() -> {timeout, 60, fun fold_deleted_test2/0}. fold_deleted_test2() -> DN = setup_testfolder("bc.test.fold_delete"), B = bitcask:open(DN, [read_write,{max_file_size, 1}]), ok = bitcask:put(B,<<"k">>,<<"v">>), ok = bitcask:delete(B,<<"k">>), true = ([] =:= bitcask:fold(B, fun(K, V, Acc0) -> [{K,V}|Acc0] end, [])), close(B), ok. lazy_open_test_() -> {timeout, 60, fun lazy_open_test2/0}. lazy_open_test2() -> DN = setup_testfolder("bc.test.opp_open"), %% Just opening/closing should not create any files. B1 = bitcask:open(DN, [read_write]), bitcask:close(B1), 0 = length(readable_files(DN)), B2 = bitcask:open(DN, [read_write]), ok = bitcask:put(B2,<<"k">>,<<"v">>), bitcask:close(B2), B3 = bitcask:open(DN, [read_write]), bitcask:close(B3), 1 = length(readable_files(DN)), ok. open_reset_open_test_() -> {timeout, 60, fun open_reset_open_test2/0}. open_reset_open_test2() -> DN = setup_testfolder("bc.test.test_twice"), B1 = bitcask:open(DN, [read_write]), ok = bitcask:put(B1,<<"k">>,<<"v">>), bitcask:close(B1), DN = setup_testfolder("bc.test.test_twice"), B2 = bitcask:open(DN, [read_write]), ok = bitcask:put(B2,<<"x">>,<<"q">>), not_found = bitcask:get(B2,<<"k">>), bitcask:close(B2). delete_merge_test_() -> {timeout, 60, fun delete_merge_test2/0}. delete_merge_test2() -> %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. Dir = setup_testfolder("bc.test.delmerge"), close(init_dataset(Dir, [{max_file_size, 1}], default_dataset())), %% perform some deletes, tombstones should go in their own files B1 = bitcask:open(Dir, [read_write,{max_file_size, 1}]), ok = bitcask:delete(B1,<<"k2">>), ok = bitcask:delete(B1,<<"k3">>), A1 = [<<"k">>], A1 = bitcask:list_keys(B1), close(B1), M = bitcask:open(Dir), ok = merge(Dir,[]), bitcask:close(M), %% Verify we've now only got one item left B2 = bitcask:open(Dir), A = [<<"k">>], A = bitcask:list_keys(B2), close(B2), ok. delete_partial_merge_test_() -> {timeout, 60, fun delete_partial_merge_test2/0}. delete_partial_merge_test2() -> %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. DN = setup_testfolder("bc.test.pardel"), close(init_dataset(DN, [{max_file_size, 1}], default_dataset())), %% perform some deletes, tombstones should go in their own files B1 = bitcask:open(DN, [read_write,{max_file_size, 1}]), ok = bitcask:delete(B1,<<"k2">>), ok = bitcask:delete(B1,<<"k3">>), A1 = [<<"k">>], A1 = bitcask:list_keys(B1), close(B1), %% selective merge, hit all of the files with deletes but not %% all of the ones with deleted data M = bitcask:open(DN), ok = merge(DN, [], {lists:reverse( lists:nthtail(2, lists:reverse(readable_files(DN)))),[] }), bitcask:close(M), %% Verify we've now only got one item left B2 = bitcask:open(DN), A = [<<"k">>], A = bitcask:list_keys(B2), close(B2), ok. corrupt_file_test_() -> {timeout, 60, fun corrupt_file_test2/0}. corrupt_file_test2() -> DN = setup_testfolder("bc.test.corrupt"), B1 = bitcask:open(DN, [read_write]), ok = bitcask:put(B1,<<"k">>,<<"v">>), {ok, <<"v">>} = bitcask:get(B1,<<"k">>), close(B1), %% write bogus data at end of hintfile, verify non-crash os:cmd("rm -rf " ++ DN ++ ".hint"), os:cmd("mkdir " ++ DN ++ ".hint"), os:cmd("cp -r " ++ DN ++ "/*hint " ++ DN ++ ".hint/100.bitcask.hint"), os:cmd("cp -r " ++ DN ++ "/*data " ++ DN ++ ".hint/100.bitcask.data"), HFN = DN ++ ".hint/100.bitcask.hint", {ok, HFD} = file:open(HFN, [append, raw, binary]), ok = file:write(HFD, <<"1">>), file:close(HFD), B2 = bitcask:open(DN ++ ".hint"), {ok, <<"v">>} = bitcask:get(B2,<<"k">>), close(B2), %% write bogus data at end of datafile, no hintfile, verify non-crash os:cmd("rm -rf " ++ DN ++ ".data"), os:cmd("mkdir " ++ DN ++ ".data"), os:cmd("cp -r " ++ DN ++ "/*data " ++ DN ++ ".data/100.bitcask.data"), DFN = DN ++ ".data/100.bitcask.data", {ok, DFD} = file:open(DFN, [append, raw, binary]), ok = file:write(DFD, <<"2">>), file:close(DFD), B3 = bitcask:open(DN ++ ".data"), {ok, <<"v">>} = bitcask:get(B3,<<"k">>), close(B3), %% as above, but more than just headersize data os:cmd("rm -rf " ++ DN ++ ".data2"), os:cmd("mkdir " ++ DN ++ ".data2"), os:cmd("cp -r " ++ DN ++ "/*data " ++ DN ++ ".data2/100.bitcask.data"), D2FN = DN ++ ".data2/100.bitcask.data", {ok, D2FD} = file:open(D2FN, [append, raw, binary]), ok = file:write(D2FD, <<"123456789012345">>), file:close(D2FD), B4 = bitcask:open(DN ++ ".data2"), {ok, <<"v">>} = bitcask:get(B4,<<"k">>), close(B4), ok. invalid_data_size_test_() -> {timeout, 60, fun invalid_data_size_test2/0}. invalid_data_size_test2() -> TestDir = setup_testfolder("bc.test.invalid_data_size_test"), TestDataFile = TestDir ++ "/1.bitcask.data", os:cmd("rm -rf " ++ TestDir), B = bitcask:open(TestDir, [read_write]), ok = bitcask:put(B,<<"k">>,<<"v">>), close(B), % Alter data size {ok, F} = file:open(TestDataFile, [read, write, raw, binary]), {ok, _} = file:position(F, {eof, -6}), ok = file:write(F, <<255:?VALSIZEFIELD>>), file:close(F), B2 = bitcask:open(TestDir), ?assertEqual({error, bad_crc}, bitcask:get(B2, <<"k">>)), close(B2), ok. testhelper_keydir_count(B) -> KD = (get_state(B))#bc_state.keydir, {KeyCount,_,_,_,_} = bitcask_nifs:keydir_info(KD), KeyCount. expire_keydir_test_() -> {timeout, 60, fun expire_keydir_test2/0}. expire_keydir_test2() -> %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. DN = setup_testfolder("bc.test.mergexpirekeydir"), close(init_dataset(DN, [{max_file_size, 1}], default_dataset())), KDB = bitcask:open(DN), %% three keys in the keydir now 3 = testhelper_keydir_count(KDB), %% Wait for it all to expire timer:sleep(2000), %% Merge everything ok = merge(DN,[{expiry_secs,1}]), %% should be no keys in the keydir now 0 = testhelper_keydir_count(KDB), bitcask:close(KDB), ok. delete_keydir_test_() -> {timeout, 60, fun delete_keydir_test2/0}. delete_keydir_test2() -> DN = setup_testfolder("bc.test.deletekeydir"), close(init_dataset(DN, [], default_dataset())), KDB = bitcask:open(DN, [read_write]), %% three keys in the keydir now 3 = testhelper_keydir_count(KDB), %% Delete something ok = bitcask:delete(KDB, <<"k">>), %% should be 2 keys in the keydir now 2 = testhelper_keydir_count(KDB), bitcask:close(KDB), ok. retry_until_true(_, _, N) when N =< 0 -> false; retry_until_true(F, Delay, Retries) -> case F() of true -> true; _ -> timer:sleep(Delay), retry_until_true(F, Delay, Retries - 1) end. no_pending_delete_bottleneck_test_() -> {timeout, 30, fun no_pending_delete_bottleneck_test2/0}. no_pending_delete_bottleneck_test2() -> % Populate B1 and B2. Then put till frozen both after reopening. % Delete all keys in both. Merge in both. Unfreeze B2. With the bug % B2 files will not be deleted. Dir1 = setup_testfolder("bc.test.del.block.1"), Dir2 = setup_testfolder("bc.test.del.block.2"), Data = default_dataset(), bitcask:close(init_dataset(Dir1, [{max_file_size, 1}], Data)), bitcask:close(init_dataset(Dir2, [{max_file_size, 1}], Data)), B1 = bitcask:open(Dir1, [read_write]), B2 = bitcask:open(Dir2, [read_write]), Files2 = readable_files(Dir2), FileDeleted = fun(Fname) -> not filelib:is_regular(Fname) end, FilesDeleted = fun() -> lists:all(FileDeleted, Files2) end, try bitcask:iterator(B1, -1, -1), put_till_frozen(B1), [begin ?assertEqual(ok, bitcask:delete(B1, K)) end || {K, _} <- Data], bitcask:merge(Dir1), bitcask:iterator(B2, -1, -1), put_till_frozen(B2), [begin ?assertEqual(ok, bitcask:delete(B2, K)) end || {K, _} <- Data], bitcask:merge(Dir2), bitcask:iterator_release(B2), %% Timing is everything. This test will timeout in 60 seconds. %% The merge delete worker works on a 1 second tick. So it should %% have at least a chance to delete the files in 10 seconds. %% Famous last words. ?assert(retry_until_true(FilesDeleted, 1000, 10)) after catch bitcask:close(B1), catch bitcask:close(B2) end. frag_status_test_() -> {timeout, 60, fun frag_status_test2/0}. frag_status_test2() -> DN = setup_testfolder("bc.test.fragtest"), B1 = bitcask:open(DN, [read_write]), ok = bitcask:put(B1,<<"k">>,<<"v">>), ok = bitcask:put(B1,<<"k">>,<<"z">>), ok = bitcask:close(B1), % close and reopen so that status can reflect a closed file B2 = bitcask:open(DN, [read_write]), {1,[{_,50,16,32}]} = bitcask:status(B2), %% 1 key, 50% frag, 16 dead bytes, 32 total bytes ok = bitcask:close(B2), ok. truncated_datafile_test_() -> {timeout, 60, fun truncated_datafile_test2/0}. truncated_datafile_test2() -> %% Mostly stolen from frag_status_test().... Dir = setup_testfolder("bc.test.truncdata"), os:cmd("rm -rf " ++ Dir), os:cmd("mkdir " ++ Dir), B1 = bitcask:open(Dir, [read_write]), [ok = bitcask:put(B1, <<"k">>, <>) || X <- lists:seq(1, 100)], ok = bitcask:close(B1), [DataFile|_] = filelib:wildcard(Dir ++ "/*.data"), truncate_file(DataFile, 540), % close and reopen so that status can reflect a closed file B2 = bitcask:open(Dir, [read_write]), {1, [{_, _, _, _}]} = bitcask:status(B2), ok = bitcask:close(B2), ok. truncated_hintfile_test() -> Dir = setup_testfolder("bc.test.trunchint"), os:cmd("rm -rf " ++ Dir), os:cmd("mkdir " ++ Dir), B1 = bitcask:open(Dir, [read_write]), [ok = bitcask:put(B1, <<"k">>, <>) || X <- lists:seq(1, 100)], ok = bitcask:close(B1), [HintFile|_] = filelib:wildcard(Dir ++ "/*.hint"), %% 1900 was determined via file inspection, may drift with version truncate_file(HintFile, 1900), B2 = bitcask:open(Dir, [read_write]), {FS, _} = get_filestate(1, get(B2)), {ok, OldVal} = application:get_env(bitcask, require_hint_crc), try application:set_env(bitcask, require_hint_crc, true), {error, {incomplete_hint, 4}} = bitcask_fileops:fold_keys( FS, fun(_, _, _, Acc) -> Acc + 1 end, 0, hintfile), application:set_env(bitcask, require_hint_crc, false), 100 = bitcask_fileops:fold_keys(FS, fun(_, _, _, Acc) -> Acc + 1 end, 0, hintfile), ok after application:set_env(bitcask, require_hint_crc, OldVal) end. trailing_junk_big_datafile_test_() -> {timeout, 60, fun trailing_junk_big_datafile_test2/0}. trailing_junk_big_datafile_test2() -> Dir = setup_testfolder("bc.test.trailingdata"), NumKeys = 400, os:cmd("rm -rf " ++ Dir), os:cmd("mkdir " ++ Dir), B1 = bitcask:open(Dir, [read_write, {max_file_size, 1024*1024*1024}]), [ok = bitcask:put(B1, <<"k", X:32>>, <>) || X <- lists:seq(1, NumKeys)], ok = bitcask:close(B1), [DataFile|_] = filelib:wildcard(Dir ++ "/*.data"), {ok, FH} = file:open(DataFile, [read, write]), {ok, _} = file:position(FH, 40*1024), ok = file:write(FH, <<0:(40*1024*8)>>), ok = file:close(FH), %% Merge everything M = bitcask:open(Dir), ok = merge(Dir), bitcask:close(M), B2 = bitcask:open(Dir, [read_write]), KeyList = bitcask:fold(B2, fun(K, _V, Acc0) -> [K|Acc0] end, []), true = length(KeyList) < NumKeys, ArbKey = 5, % get arbitrary key near start {ok, <>} = bitcask:get(B2, <<"k", ArbKey:32>>), ok = bitcask:close(B2), ok. truncated_merge_test_() -> {timeout, 60, fun truncated_merge_test2/0}. truncated_merge_test2() -> Dir = setup_testfolder("bc.test.truncmerge"), os:cmd("rm -rf " ++ Dir), os:cmd("mkdir " ++ Dir), %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. %% If anyone ever modifies default_dataset() to return fewer than 3 %% elements, this test will break. DataSet = default_dataset() ++ [{<<"k98">>, <<"v98">>}, {<<"k99">>, <<"v99">>}], close(init_dataset(Dir, [{max_file_size, 1}], DataSet)), timer:sleep(900), %% Verify number of files in directory 5 = length(readable_files(Dir)), [Data1, Data2, _, _, Data5|_] = filelib:wildcard(Dir ++ "/*.data"), [_, _, Hint3, Hint4|_] = filelib:wildcard(Dir ++ "/*.hint"), %% Truncate 1st after data file's header, read by bitcask_fileops:fold/3). %% Truncate 2nd in the middle of header, which provokes another %% variation of bug mentioned by Bugzilla 1097. %% Truncate 3rd after hint file's header, read by %% bitcask_fileops:fold_hintfile/3). %% Truncate 4th in the middle of header, same situation as 2nd.... %% Truncate 5th data file at byte #1. ok = truncate_file(Data1, 15), ok = truncate_file(Data2, 5), ok = truncate_file(Hint3, 15), ok = truncate_file(Hint4, 5), ok = corrupt_file(Data5, 15, <<"!">>), %% Merge everything M = bitcask:open(Dir), ok = merge(Dir), bitcask:close(M), ok = bitcask_merge_delete:testonly__delete_trigger(), %% Verify we've now only got one file 1 = length(readable_files(Dir)), %% Make sure all corrupted data is missing, all good data is present B = bitcask:open(Dir), BadKeys = [<<"k">>, <<"k2">>, % Trunc of Data1 & Data2 <<"k99">>], % Trunc of Data5 {BadData, GoodData} = lists:partition(fun({K, _V}) -> lists:member(K, BadKeys) end, DataSet), lists:foldl(fun({K, _V} = KV, _) -> {KV, not_found} = {KV, bitcask:get(B, K)} end, undefined, BadData), lists:foldl(fun({K, V} = KV, _) -> {KV, {ok, V}} = {KV, bitcask:get(B, K)} end, undefined, GoodData), ok = bitcask:close(B). truncate_file(Path, Offset) -> {ok, FH} = file:open(Path, [read, write]), {ok, Offset} = file:position(FH, Offset), ok = file:truncate(FH), file:close(FH). corrupt_file(Path, Offset, Data) -> {ok, FH} = file:open(Path, [read, write]), {ok, Offset} = file:position(FH, Offset), ok = file:write(FH, Data), file:close(FH). % Verify that if the cached efile port goes away, we can recover % and not get stuck opening casks -ifdef(dirty_file_nif). efile_error_test() -> ok. -else. efile_error_test() -> Dir = setup_testfolder("bc.efile.error"), B = bitcask:open(Dir, [read_write]), ok = bitcask:put(B, <<"k">>, <<"v">>), ok = bitcask:close(B), Port = get(bitcask_efile_port), % If this fails, we stopped using the efile port trick to list % dir contents, so remove this test ?assert(is_port(Port)), true = erlang:port_close(Port), case bitcask:open(Dir) of {error, _} = Err -> ?assertEqual(ok, Err); B2 when is_reference(B2) -> ok = bitcask:close(B2) end. -endif. %% About leak_t0(): %% %% If bitcask leaks file descriptors for the 'touch'ed files, output is: %% Res = 920 %% %% If bitcask isn't leaking the 'touch'ed files, output is: %% Res = 20 leak_t0() -> Dir = setup_testfolder("goofus"), _Ref0 = bitcask:open(Dir), os:cmd("cd " ++ Dir ++ "; sh -c 'for i in `seq 100 1000`; do touch $i.bitcask.data $i.bitcask.hint; done'"), _Ref1 = bitcask:open(Dir), Cmd = lists:flatten(io_lib:format("lsof -nP -p ~p | wc -l", [os:getpid()])), io:format("Res = ~s\n", [os:cmd(Cmd)]). leak_t1() -> Dir = setup_testfolder("goofus"), NumKeys = 300, os:cmd("rm -rf " ++ Dir), Ref = bitcask:open(Dir, [read_write, {max_file_size, 10}]), Used = fun() -> Cmd = lists:flatten(io_lib:format("lsof -nP -p ~p | wc -l", [os:getpid()])), %% io:format("Cmd = ~s\n", [Cmd]), os:cmd(Cmd) end, [bitcask:put(Ref, <>, <<"it's a big, big world!">>) || X <- lists:seq(1, NumKeys)], io:format("After putting ~p keys, lsof says: ~s", [NumKeys, Used()]), DelKeys = NumKeys div 2, [bitcask:delete(Ref, <>) || X <- lists:seq(1, DelKeys)], io:format("After deleting ~p keys, lsof says: ~s", [DelKeys, Used()]), bitcask:merge(Dir), io:format("After merging, lsof says: ~s", [Used()]), io:format("Q: Are all keys fetchable? ..."), [{ok, _} = bitcask:get(Ref, <>) || X <- lists:seq(DelKeys + 1, NumKeys)], [not_found = bitcask:get(Ref, <>) || X <- lists:seq(1, DelKeys)], io:format("A: yes\n"), io:format("Now, lsof says: ~s", [Used()]), ok. slow_folder(Cask) -> Owner = receive {owner, O} -> O end, SlowCollect = fun(K, V, Acc) -> if Acc == [] -> Owner ! i_have_started_folding, receive go_ahead_with_fold -> ok end; true -> ok end, receive go -> ok; go_reply -> Owner ! reply, ok end, [{K, V} | Acc] end, B = bitcask:open(Cask), L = fold(B, SlowCollect, [], -1, -1, false), Owner ! {slow_folder_done, self(), L}, bitcask:close(B). finish_worker_loop2(Pid) -> receive {slow_folder_done, Pid, L} -> L after 0 -> Pid ! go, finish_worker_loop2(Pid) end. freeze_close_reopen_test_() -> {timeout, 120, fun() -> freeze_close_reopen() end}. freeze_close_reopen() -> Cask = setup_testfolder("bc.test.freeze_close_reopen_test" ++ os:getpid()), %% khash.h has an __ac_prime_list[] entry at 11, and 70% of 11 is %% approximately 8. So choose # of keys for Data to be a bit %% below 8, and then # of keys for Data2 will definitely be %% beyond the khash resizing point, e.g. 5x. Keys = 7, Data = [{<>, <>} || K <- lists:seq(1, Keys)], DelKey = 2, Data2 = [{<>, <<(K+1):32>>} || K <- lists:seq(1, Keys*5), K /= DelKey], B = init_dataset(Cask, Data), try CollectAll = fun(K, V, Acc) -> [{K, V} | Acc] end, PutData = fun(DataList) -> [begin ok = put(B, K, V) end || {K, V} <- DataList] end, ?assertEqual(Data, lists:sort(fold(B, CollectAll, [], -1, -1, false))), if true -> State = get_state(B), put_state(B, State#bc_state{max_file_size = 0}) end, Pid = spawn(fun() -> slow_folder(Cask) end), Pid ! {owner, self()}, receive i_have_started_folding -> ok after 10*1000 -> error(timeout_should_never_happen) end, %% The difference between the fold_visits_frozen_test test and %% this test is that fold_visits_frozen_test will put %% random-and-expired-and-thus-invisible keys into the keydir %% to freeze it before it modifies any keys that are in the k* %% range. This test modifies keys in the k* range %% immediately. PutData(Data2), %% We must be able to check that both kinds of mutation are %% tested .... delete a key also! ok = delete(B, <>), not_found = get(B, <>), %% Sanity check [{ok, V} = get(B, K) || {K, V} <- Data2], not_found = get(B, <>), ok = close(B), B2 = open(Cask, [read_write]), [{ok, V} = get(B2, K) || {K, V} <- Data2], not_found = get(B2, <>), %% It is too difficult here to figure out what fold would tell %% us. The multi-folder stuff will allow additions to be made %% as long as the khash doesn't resize. %% Unfreeze the keydir, waiting until complete Pid ! go_ahead_with_fold, L1a = finish_worker_loop2(Pid), %% Checking here is a bit crazy. %% 1. The first item that the worker's fold found was *prior* %% to our first mutation by PutData(). But the hash table %% scrambles key order, so we have to cheat by assuming that %% the last item in the folder's accumulator is the first %% key visited by the fold. %% 2. All of the other keys that were visited by the folder %% should appear frozen, so we compare them to *Data*. {FirstItemKey, _FirstItemValue} = FirstItemFound = lists:last(L1a), [ExpectedFirstItemFound] = [KV || KV = {K, _} <- Data, K == FirstItemKey], ?assertEqual(ExpectedFirstItemFound, FirstItemFound), ?assertEqual([KV || KV = {K, _} <- Data, K /= FirstItemKey], lists:sort(L1a) -- [FirstItemFound]), %% Check that we see the updated data yet again L3 = fold(B2, CollectAll, [], -1, -1, false), ?assertEqual(Data2, lists:sort(L3)), [{ok, V} = get(B2, K) || {K, V} <- Data2], not_found = get(B2, <>), bitcask:close(B2), ok after bitcask_time:test__clear_fudge(), catch bitcask:close(B), os:cmd("rm -rf " ++ Cask) end. fold_itercount_test_() -> {timeout, 60, fun fold_itercount_test2/0}. fold_itercount_test2() -> Cask = setup_testfolder("bc.itercount-test"), Ref = bitcask:open(Cask, [read_write]), try %% populate the store a little [bitcask:put(Ref, <>, <>) || X <- lists:seq(1, 100)], %% open a few slow folders Pids = [spawn(fun() -> slow_folder(Cask) end) || _ <- lists:seq(1,10)], [ Pid ! {owner, self()} || Pid <- Pids], [receive i_have_started_folding -> ok end || _ <- Pids], KD = (get_state(Ref))#bc_state.keydir, Info = bitcask_nifs:keydir_info(KD), {_,_,_,{_,Count,_,_},_} = Info, ?assertEqual(length(Pids), Count), %% kill them all dead [ exit(Pid, brutal_kill) || Pid <- Pids], [erlang:garbage_collect(Pid) || Pid <- processes()], %% collect the iterator information and make sure that the %% count is still 0 Info2 = bitcask_nifs:keydir_info(KD), {_,_,_,{_,Count2,_,_},_} = Info2, ?assertEqual(0, Count2) after ok = bitcask:close(Ref), os:cmd("rm -rf "++Cask) end. fold_lockstep_test_() -> {timeout, 100, fun fold_lockstep_body/0}. fold_lockstep_body() -> Cask = setup_testfolder("bc.lockstep-test"), Ref = bitcask:open(Cask, [read_write]), try Initial = 1500, %% has to be large to avoid resize behavior. %% populate the store a little [bitcask:put(Ref, <>, <>) || X <- lists:seq(1, Initial)], Folders = 5, Me = self(), %%io:format(user, "spawning folders~n", []), Pids = [begin P = spawn(fun() -> slow_folder(Cask) end), P ! {owner, Me}, receive i_have_started_folding -> ok end, P ! go_ahead_with_fold, [bitcask:put(Ref, <>, <>) || X <- lists:seq(Initial + 1 + 100*(N-1), Initial + 100*N)], {P, Initial + 100*(N-1)} end || N <- lists:seq(1, Folders)], %%io:format(user, "iterating folders in step~n", []), [begin [begin case I =< N of true -> P ! go_reply, receive reply -> ok end; false -> ok end end|| {P,N} <- Pids], %%io:format(user, "step~n", []), timer:sleep(5) end || I <- lists:seq(1, Initial + 100 * Folders)], %%io:format(user, "collecting output~n", []), [begin receive {slow_folder_done, P, L} -> ?assertEqual(N, length(L)) after 1000 -> throw(maybe_deadlock) end end || {P,N} <- Pids], ok after ok = bitcask:close(Ref), os:cmd("rm -rf "++Cask) end. no_tombstones_after_reopen_test_() -> {timeout, 60, ?_test(no_tombstones_after_reopen_test2(false))}. zap_hints_no_tombstones_after_reopen_test_() -> {timeout, 60, ?_test(no_tombstones_after_reopen_test2(true))}. no_tombstones_after_reopen_test2(DeleteHintFilesP) -> Dir = setup_testfolder("bc.test.truncmerge"), MaxFileSize = 100, os:cmd("rm -rf " ++ Dir), os:cmd("mkdir " ++ Dir), %% Initialize dataset with max_file_size set to 1 so that each file will %% only contain a single key. %% If anyone ever modifies default_dataset() to return fewer than 3 %% elements, this test will break. KVs = [{<>, <>} || X <- lists:seq(33, 52)], DataSet = default_dataset() ++ KVs, B = init_dataset(Dir, [{max_file_size, MaxFileSize}], DataSet), [bitcask:delete(B, <>) || X <- lists:seq(40, 41)], close(B), if DeleteHintFilesP -> os:cmd("rm " ++ Dir ++ "/*.hint"); true -> ok end, B2 = bitcask:open(Dir, [read_write, {max_file_size, MaxFileSize}]), Res1 = bitcask:fold(B2, fun(K, _V, Acc0) -> [K|Acc0] end, [], -1, -1, true), ?assertNotEqual([], [X || {tombstone, _} = X <- Res1]), Res2 = bitcask:fold_keys(B2, fun(K, Acc0) -> [K|Acc0] end, [], -1, -1, true), ?assertEqual([], [X || {tombstone, _} = X <- Res2]), ok = bitcask:close(B2). update_tstamp_stats_test_() -> {timeout, 60, fun update_tstamp_stats_test2/0}. update_tstamp_stats_test2() -> Dir = setup_testfolder("bc.tstamp.stats"), bitcask_time:test__clear_fudge(), %% cleanup after previous test bitcask_time:test__set_fudge(1), try B = init_dataset(Dir, [read_write, {max_file_size, 1000000}], []), Write = fun(KVs) -> [ begin bitcask:put(B, K, V), bitcask_time:test__incr_fudge(1) end || {K, V} <- KVs] end, Write([{<<"k1">>, <<"v1">>}, {<<"k2">>, <<"v2">>}, {<<"k3">>, <<"v3">>}]), ok = bitcask:close(B), B2 = bitcask:open(Dir), ?assertMatch({3, [#file_status{oldest_tstamp=1, newest_tstamp=3}]}, summary_info(B2)), bitcask:close(B2) after bitcask_time:test__clear_fudge() end. scan_err_test_() -> {setup, fun() -> meck:new(bitcask_fileops, [passthrough]), ok end, fun(_) -> meck:unload() end, [fun() -> Dir = setup_testfolder("bc.scan.err"), meck:expect(bitcask_fileops, data_file_tstamps, fun(_) -> {error, because} end), ?assertMatch({error, _}, bitcask:open(Dir)), meck:unload(bitcask_fileops), B = bitcask:open(Dir), ?assertMatch({true, B}, {is_reference(B), B}), ok = bitcask:close(B) end]}. %% Make sure that an error in the user provided key transformation function %% does not bring the whole thing down. It's possible to hit this in Riak %% when finding keys from a downgrade and such. no_crash_on_key_transform_test() -> Dir = setup_testfolder("bc.key.tx.crash"), CrashTx = fun(_K) -> throw(naughty_key_transform_failure) end, B = init_dataset(Dir, [read_write, {key_transform, CrashTx}], default_dataset()), bitcask:list_keys(B), bitcask:fold(B, fun(K, _V, Acc0) -> [K|Acc0] end, [], -1, -1, true), bitcask:merge(Dir, [{key_transform, CrashTx}]), ok = bitcask:close(B), B2 = bitcask:open(Dir, [{key_transform, CrashTx}]), ok = bitcask:close(B2), ok. total_byte_stats_test_() -> {timeout, 60, fun total_byte_stats_test2/0}. total_byte_stats_test2() -> Dir = setup_testfolder("bc.total.byte.stats"), B = init_dataset(Dir, [read_write, {max_file_size, 1}], []), [begin case Op of {del, K} -> bitcask:delete(B, K); {K, V} -> bitcask:put(B, K, V) end end || Op <- [{<<"k1">>, <<"1">>}, {<<"k2">>, <<"2">>}, {del, <<"k1">>}, {<<"k1">>, <<"3">>}, {<<"k2">>, <<"4">>}]], {_KCount, FStats} = summary_info(B), Files1 = lists:sort([{File, Size} || #file_status{filename=File, total_bytes=Size} <- FStats]), ExpFiles1 = [{Dir ++ "/1.bitcask.data", 2 + 1 + ?HEADER_SIZE}, {Dir ++ "/2.bitcask.data", 2 + 1 + ?HEADER_SIZE}, {Dir ++ "/3.bitcask.data", 2 + ?TOMBSTONE2_SIZE + ?HEADER_SIZE}, {Dir ++ "/4.bitcask.data", 2 + 1 + ?HEADER_SIZE}], ?assertEqual(ExpFiles1, Files1), bitcask:close(B). merge_batch_test_() -> {timeout, 100, fun merge_batch_test2/0}. merge_batch_test2() -> Dir = setup_testfolder("bc.merge.batch"), % Create a valid Bitcask dir with files 10-20 present only DataSet = [{integer_to_binary(N), <<"data">>} || N <- lists:seq(1,20)], close(init_dataset(Dir, [{max_file_size, 1}], DataSet)), BFile = fun(N) -> filename:join(Dir, integer_to_list(N) ++ ".bitcask.data") end, % Simple transform for compact test data representation below % Numbers -> binary bitcask file name, with 0 -> <<>> MData = fun(L) -> [case N of blank -> "\n"; _ -> integer_to_list(N)++".bitcask.data\n" end || N <- L] end, % Number to full path bitcask file transform ExpData = fun(L) -> [BFile(N) || N <- L] end, ok = file:write_file(filename:join(Dir, "merge.txt"), MData([7, 9, 10, blank, 12, 13, blank, 14])), B = bitcask:open(Dir), try ?assertEqual({true, {ExpData([7, 9, 10]), []}}, bitcask:needs_merge(B)), ok = bitcask:merge(Dir, [], [BFile(7), BFile(9)]), ?assertEqual({true, {ExpData([10]), []}}, bitcask:needs_merge(B)), ok = bitcask:merge(Dir, [], [BFile(10)]), ?assertEqual({true, {ExpData([12, 13]), []}}, bitcask:needs_merge(B)), ok = bitcask:merge(Dir, [], [BFile(12)]), ?assertEqual({true, {ExpData([13]), []}}, bitcask:needs_merge(B)), ok = bitcask:merge(Dir, [], [BFile(13)]), ?assertEqual({true, {ExpData([14]), []}}, bitcask:needs_merge(B)) after bitcask:close(B) end. merge_expired_test_() -> {timeout, 120, fun merge_expired_test2/0}. merge_expired_test2() -> Dir = setup_testfolder("bc.merge.expired.files"), NKeys = 10, KF = fun(N) -> <> end, KVGen = fun(S, E) -> [{KF(N), <<"v">>} || N <- lists:seq(S, E)] end, DataSet = KVGen(1, 3), B = init_dataset(Dir, [{max_file_size, 1}], DataSet), ok = bitcask:delete(B, KF(1)), put_kvs(B, KVGen(4, NKeys)), % Merge away the first 4 files as if they were completely expired, FirstFiles = [Dir ++ "/" ++ integer_to_list(N) ++ ".bitcask.data" || N <- lists:seq(1, 4)], ?assertEqual(ok, bitcask:merge(Dir, [], {FirstFiles, FirstFiles})), ExpectedKeys = [KF(N) || N <- lists:seq(4, NKeys)], ActualKeys1 = lists:sort(bitcask:list_keys(B)), ActualKeys2 = lists:sort(bitcask:fold(B, fun(K,_V,A)->[K|A] end, [])), bitcask:close(B), ?assertEqual(ExpectedKeys, ActualKeys1), ?assertEqual(ExpectedKeys, ActualKeys2). max_merge_size_test_() -> {timeout, 120, fun max_merge_size_test2/0}. max_merge_size_test2() -> Dir = setup_testfolder("bc.max.merge.size"), % Generate 10 objects roughly 100 bytes each, one per file DataSet = [{<>, <<0:100/integer-unit:8>>} || N <- lists:seq(1, 10)], B0 = init_dataset(Dir, [{max_file_size, 1}], DataSet), _ = [ok = bitcask:delete(B0, <>) || N <- lists:seq(1,10)], ok = bitcask:close(B0), AllFiles = readable_files(Dir), ?assert(length(AllFiles) > 10), % Write explicit merge file with every file ok = file:write_file(filename:join(Dir, "merge.txt"), [[filename:basename(F), <<"\n">>] || F <- AllFiles]), SizeFn = fun(L) -> lists:foldl(fun(F, Acc) -> {ok, #file_info{size=S}} = file:read_file_info(F), Acc + S end, 0, L) end, B = bitcask:open(Dir), ?assertEqual({true, {AllFiles, []}}, bitcask:needs_merge(B)), try MaxSizes = [0, 10, 100, 150, 250, 500], [begin {true, {Files, []}} = bitcask:needs_merge(B, [{max_merge_size, MaxSize}]), Size = SizeFn(Files), %?debugFmt("Size ~p, max size ~p : ~p\n", [Size, MaxSize, Files]), ?assertEqual({Size, MaxSize, true}, {Size, MaxSize, Size =< MaxSize}) end || MaxSize <- MaxSizes] after bitcask:close(B) end. % This verifies that legacy tombstones, which were not safe to drop on partial % merges are eventually dropped. They are first converted to version 1 % tombstones on a merge. These contain a file id. When all files up to that one % have gone away, they become safe to drop. legacy_tombstones_test_() -> {timeout, 60, fun legacy_tombstones_test2/0}. legacy_tombstones_test2() -> Dir = setup_testfolder("bc.legacy.tombstones"), % Data: 10 keys, delete those 10, write an extra one. DataSet = [{<>, <<0>>} || N <- lists:seq(1, 10)], B0 = init_dataset(Dir, [{max_file_size, 1}], DataSet), _ = [ok = bitcask:delete(B0, <>) || N <- lists:seq(1,10)], ok = bitcask:put(B0, <<11:32>>, <<0>>), ok = bitcask:close(B0), B = bitcask:open(Dir), % Merge all but last file, which should generate 10 v1 tombstones % For those first 10 values written and deleted AllFiles = readable_files(Dir), L1 = length(AllFiles), AllButLast = lists:sublist(AllFiles, L1-1), Last = lists:nth(L1, AllFiles), ok = merge(Dir, [], AllButLast), % Merge the files containing the v1 tombstones, skip the live one. % Notice that this is not a "prefix" merge that would drop any kind of % tombstone since we are skipping the first file. AllFiles2 = readable_files(Dir), ?assertEqual(hd(AllFiles2), Last), AllButFirst = tl(AllFiles2), ok = merge(Dir, [], AllButFirst), % Now we should have the file with the only live object followed by % files with v1 tombstones. When merging those, since all of the original % files containing the values and tombstones are gone, all v1 tombstones % should be dropped, leaving only the lonely file with the live object. AllFiles3 = readable_files(Dir), bitcask:close(B), ?assertEqual([Last], AllFiles3). update_tombstones_test() -> Dir = setup_testfolder("bc.update.tombstones"), Key = <<"k">>, Data = [{Key, integer_to_binary(N)} || N <- lists:seq(1, 10)], B = init_dataset(Dir, [read_write, {max_file_size, 50000000}], Data), ok = bitcask:close(B), % Re-open to guarantee opening a second file. % An update on the new file requires a tombstone. B2 = bitcask:open(Dir, [read_write, {max_file_size, 50000000}]), ok = bitcask:put(B2, Key, <<"last_val">>), ok = bitcask:close(B2), Files = bitcask:readable_files(Dir), Fds = [begin {ok, Fd} = bitcask_fileops:open_file(File), Fd end || File <- Files], CountF = fun(_K, V, _Tstamp, _, Acc) -> case bitcask:is_tombstone(V) of true -> Acc + 1; false -> Acc end end, TombCount = bitcask:subfold(CountF, Fds, 0), ?assertEqual(1, TombCount). -endif. bitcask-2.1.0/src/bitcask_sup.erl0000644000232200023220000000337413655023466017323 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_sup). -behaviour(supervisor). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -endif. %% API -export([start_link/0]). %% Supervisor callbacks -export([init/1]). %% Helper macro for declaring children of supervisor -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). %% =================================================================== %% API functions %% =================================================================== start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). %% =================================================================== %% Supervisor callbacks %% =================================================================== init([]) -> {ok, {{one_for_one, 5, 10}, [?CHILD(bitcask_merge_worker, worker), ?CHILD(bitcask_merge_delete, worker)]}}. bitcask-2.1.0/src/bitcask_merge_worker.erl0000644000232200023220000002112213655023466021173 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_merge_worker). -behaviour(gen_server). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -endif. -ifdef(TEST). -ifdef(EQC). -include_lib("eqc/include/eqc.hrl"). -export([prop_in_window/0]). -endif. -include_lib("eunit/include/eunit.hrl"). -endif. %% API -export([start_link/0, merge/1, merge/2, merge/3, status/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { queue :: list(), worker :: undefined | pid()}). %% ==================================================================== %% API %% ==================================================================== start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). merge(Dir) -> merge(Dir, []). merge(Dir, Opts) -> gen_server:call(?MODULE, {merge, [Dir, Opts]}, infinity). merge(Dir, Opts, Files) -> gen_server:call(?MODULE, {merge, [Dir, Opts, Files]}, infinity). status() -> gen_server:call(?MODULE, {status}, infinity). %% ==================================================================== %% gen_server callbacks %% ==================================================================== init([]) -> %% Trap exits of the actual worker process process_flag(trap_exit, true), %% Use a dedicated worker sub-process to do the actual merging. The process %% may ignore messages for a long while during the merge and we want to %% ensure that our message queue doesn't fill up with a bunch of dup %% requests for the same directory. %% %% The sub-process is created per-merge request to ensure that any %% ports/file handles opened during the merge get properly cleaned up, even %% in error cases. {ok, #state{ queue = [] }}. handle_call({merge, Args0}, _From, #state { queue = Q } = State) -> [Dirname|_] = Args0, Args1 = case length(Args0) of 3 -> %% presort opts and files tuples for better matches %% and less work later [_, Opts0, Tuple0] = Args0, {Files, Expired} = Tuple0, Opts = lists:usort(Opts0), Tuple = {lists:usort(Files), lists:usort(Expired)}, [Dirname, Opts, Tuple]; _ -> %% whole directory don't need to be sorted Args0 end, Args = list_to_tuple(Args1), %% convert back and forth from tuples to lists so we can use %% keyfind case lists:keyfind(Dirname, 1, Q) of Args -> {reply, already_queued, State}; Partial when is_tuple(Partial) -> New = merge_items(Args, Partial), Q1 = lists:keyreplace(Dirname, 1, Q, New), {reply, ok, State#state{ queue = Q1 }}; false -> case State#state.worker of undefined -> WorkerPid = spawn_link(fun() -> do_merge(Args0) end), {reply, ok, State#state { worker = WorkerPid }}; _ -> {reply, ok, State#state { queue = Q ++ [Args] }} end end; handle_call({status}, _From, #state { queue = Q, worker = Worker } = State) -> {reply, {length(Q), Worker}, State}; handle_call(_, _From, State) -> {reply, unknown_call, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info({'EXIT', _Pid, normal}, #state { queue = Q } = State) -> case Q of [] -> {noreply, State#state { worker = undefined }}; [Args0|Q2] -> Args = tuple_to_list(Args0), WorkerPid = spawn_link(fun() -> do_merge(Args) end), {noreply, State#state { queue = Q2, worker = WorkerPid }} end; handle_info({'EXIT', Pid, Reason}, #state { worker = Pid } = State) -> error_logger:error_msg("Merge worker PID exited: ~p\n", [Reason]), {stop, State}. terminate(_Reason, State) -> catch exit(State#state.worker, shutdown), ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %% ==================================================================== %% private functions %% ==================================================================== merge_items(New, Old) -> %% first element will always match Dirname = element(1, New), %% old args are already sorted OOpts = element(2, Old), NOpts = lists:usort(element(2, New)), Opts = lists:umerge(OOpts, NOpts), case {size(New), size(Old)} of {3, 3} -> Files = merge_files(element(3, New), element(3, Old)), {Dirname, Opts, Files}; {2, 2} -> {Dirname, Opts}; {2, 3} -> {Dirname, Opts, element(3, Old)}; {3, 2} -> {Dirname, Opts, element(3, New)} end. merge_files(New, Old) -> {NFiles, NExp} = New, {OFiles, OExp} = Old, %% old files and expired lists are already sorted Files0 = lists:umerge(lists:usort(NFiles), OFiles), Expired = lists:umerge(lists:usort(NExp), OExp), Files = Files0 -- Expired, {Files, Expired}. %% ==================================================================== %% Internal worker %% ==================================================================== do_merge(Args) -> {_, {Hour, _, _}} = calendar:local_time(), case in_merge_window(Hour, merge_window()) of true -> Start = os:timestamp(), Result = (catch apply(bitcask, merge, Args)), ElapsedSecs = timer:now_diff(os:timestamp(), Start) / 1000000, [_,_,Args3] = Args, case Result of ok -> error_logger:info_msg("Merged ~p in ~p seconds.\n", [Args3, ElapsedSecs]); {Error, Reason} when Error == error; Error == 'EXIT' -> error_logger:error_msg("Failed to merge ~p: ~p\n", [Args3, Reason]) end; false -> ok end. merge_window() -> case application:get_env(bitcask, merge_window) of {ok, always} -> always; {ok, never} -> never; {ok, {StartHour, EndHour}} when StartHour >= 0, StartHour =< 23, EndHour >= 0, EndHour =< 23 -> {StartHour, EndHour}; Other -> error_logger:error_msg("Invalid bitcask_merge window specified: ~p. " "Defaulting to 'always'.\n", [Other]), always end. in_merge_window(_NowHour, always) -> true; in_merge_window(_NowHour, never) -> false; in_merge_window(NowHour, {Start, End}) when Start =< End -> (NowHour >= Start) and (NowHour =< End); in_merge_window(NowHour, {Start, End}) when Start > End -> (NowHour >= Start) or (NowHour =< End). %% ==================================================================== %% Unit tests %% ==================================================================== -ifdef(EQC). prop_in_window() -> ?FORALL({NowHour, WindowLen, StartTime}, {choose(0, 23), choose(0, 23), choose(0, 23)}, begin EndTime = (StartTime + WindowLen) rem 24, %% Generate a set of all hours within this window WindowHours = [H rem 24 || H <- lists:seq(StartTime, StartTime + WindowLen)], %% If NowHour is in the set of windows hours, we expect our function %% to indicate that we are in the window ExpInWindow = lists:member(NowHour, WindowHours), ?assertEqual(ExpInWindow, in_merge_window(NowHour, {StartTime, EndTime})), true end). -endif. bitcask-2.1.0/src/stacktrace.hrl0000644000232200023220000000230013655023466017127 0ustar debalancedebalance%% Originating from Quviq AB %% Fix to make Erlang programs compile on both OTP20 and OTP21. %% %% Get the stack trace in a way that is backwards compatible. Luckily %% OTP_RELEASE was introduced in the same version as the new preferred way of %% getting the stack trace. A _catch_/2 macro is provided for consistency in %% cases where the stack trace is not needed. %% %% Example use: %% try f(...) %% catch %% ?_exception_(_, Reason, StackToken) -> %% case Reason of %% {fail, Error} -> ok; %% _ -> {'EXIT', Reason, ?_get_stacktrace_(StackToken)} %% end %% end, -ifdef(OTP_RELEASE). %% This implies 21 or higher -define(_exception_(Class, Reason, StackToken), Class:Reason:StackToken). -define(_get_stacktrace_(StackToken), StackToken). -define(_current_stacktrace_(), try exit('$get_stacktrace') catch exit:'$get_stacktrace':__GetCurrentStackTrace -> __GetCurrentStackTrace end). -else. -define(_exception_(Class, Reason, _), Class:Reason). -define(_get_stacktrace_(_), erlang:get_stacktrace()). -define(_current_stacktrace_(), erlang:get_stacktrace()). -endif. bitcask-2.1.0/src/bitcask_merge_delete.erl0000644000232200023220000005565013655023466021141 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_merge_delete). -behaviour(gen_server). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_side_effect, [{file, '_', '_'}, {bitcask_nifs, '_', '_'}]}). -endif. %% API -export([start_link/0, defer_delete/3, queue_length/0]). -export([testonly__delete_trigger/0, testonly__truncate_hint/2, testonly__create_stale_lock/0]).% testing only %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include("bitcask.hrl"). -include_lib("kernel/include/file.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -compile([export_all, nowarn_export_all]). -endif. -define(SERVER, ?MODULE). -define(TIMEOUT, 1000). -define(TEST_DIR, filename:join(?TEST_FILEPATH, os:getpid())). -ifdef(namespaced_types). -type merge_queue() :: queue:queue(). -else. -type merge_queue() :: queue(). -endif. -record(state, {q :: merge_queue()}). %%%=================================================================== %%% API %%%=================================================================== start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). defer_delete(Dirname, IterGeneration, Files) -> gen_server:call(?SERVER, {defer_delete, Dirname, IterGeneration, Files}, infinity). queue_length() -> gen_server:call(?SERVER, {queue_length}, infinity). testonly__delete_trigger() -> gen_server:call(?SERVER, {testonly__delete_trigger}, infinity). %% this is a common function to both eunit and EQC testonly__truncate_hint(Seed, TruncBy0) -> case filelib:wildcard(?TEST_DIR ++ "/*.hint") of [] -> ok; Hints-> Hint = lists:nth(1 + (abs(Seed) rem length(Hints)), Hints), {ok, Fi} = file:read_file_info(Hint), {ok, Fh} = file:open(Hint, [read, write]), TruncBy = (1 + abs(TruncBy0)) rem (Fi#file_info.size+1), {ok, _To} = file:position(Fh, {eof, erlang:max(-TruncBy, 0)}), %% io:format(user, "Truncating ~p by ~p to ~p\n", [Hint, TruncBy, _To]), file:truncate(Fh), file:close(Fh) end. testonly__create_stale_lock() -> Fname = filename:join(?TEST_DIR, "bitcask.write.lock"), filelib:ensure_dir(Fname), ok = file:write_file(Fname, "102349430239 abcdef\n"). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([]) -> {ok, #state{q = queue:new()}, ?TIMEOUT}. handle_call({defer_delete, Dirname, IterGeneration, Files}, _From, State) -> {reply, ok, State#state{q = queue:in({Dirname, IterGeneration, Files}, State#state.q)}, ?TIMEOUT}; handle_call({queue_length}, _From, State) -> {reply, queue:len(State#state.q), State, ?TIMEOUT}; handle_call({testonly__delete_trigger}, _From, State) -> {reply, ok, delete_ready_files(State), ?TIMEOUT}; handle_call(_Request, _From, State) -> Reply = unknown_request, {reply, Reply, State, ?TIMEOUT}. handle_cast(_Msg, State) -> {noreply, State, ?TIMEOUT}. handle_info(timeout, State) -> {noreply, delete_ready_files(State), ?TIMEOUT}; handle_info(_Info, State) -> {noreply, State, ?TIMEOUT}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== delete_ready_files(S) -> delete_ready_files(S, queue:new()). delete_ready_files(S, PendingQ) -> case queue:out(S#state.q) of {empty, _} -> S#state{q = PendingQ}; {{value, {Dirname, IterGeneration, Files} = Entry}, NewQ} -> {_, KeyDir} = bitcask_nifs:keydir_new(Dirname), try {_,_,_,IterStatus,_} = bitcask_nifs:keydir_info(KeyDir), ReadyToDelete = case IterStatus of {_, _, false, _} -> true; {CurGen, _, true, _} when CurGen > IterGeneration -> true; _ -> false end, S2 = S#state{q = NewQ}, case ReadyToDelete of true -> delete_files(Files), bitcask_nifs:keydir_release(KeyDir), delete_ready_files(S2, PendingQ); false -> delete_ready_files(S2, queue:in(Entry, PendingQ)) end catch _X:_Y -> %% Not sure what problem was: keydir is no longer %% valid, or a problem deleting files, but in any %% case we're going to wash our hands of the matter and %% let the next merge clean up. S#state{q = NewQ} after catch bitcask_nifs:keydir_release(KeyDir) end end. delete_files(Files) -> lists:foreach(fun bitcask_fileops:delete/1, Files). -ifdef(TEST). multiple_merges_during_fold_test_() -> {timeout, 60, fun multiple_merges_during_fold_body/0}. multiple_merges_during_fold_body() -> Dir = filename:join(?TEST_FILEPATH, "bc.multiple-merges-fold"), B = bitcask:open(Dir, [read_write, {max_file_size, 50}]), PutSome = fun() -> [bitcask:put(B, <>, <<"yo this is a value">>) || X <- lists:seq(1,5)] end, PutSome(), PutSome(), Bstuff = get(B), FoldFun = fun(_K, _V, 0) -> receive go_ahead -> ok end, 1; (_K, _V, 1) -> 1 end, SlowPid = spawn(fun() -> put(B, Bstuff), bitcask:fold(B, FoldFun, 0) end), CountSetuids = fun() -> Fs = filelib:wildcard(Dir ++ "/*"), length([F || F <- Fs, bitcask:has_pending_delete_bit(F)]) end, PutSome(), Count1 = merge_until(Dir, 0, CountSetuids), PutSome(), bitcask:merge(Dir), PutSome(), merge_until(Dir, Count1, CountSetuids), SlowPid ! go_ahead, timer:sleep(500), ok = ?MODULE:testonly__delete_trigger(), 0 = CountSetuids(), ok. merge_until(Dir, MinCount, CountSetuids) -> bitcask:merge(Dir), Count = CountSetuids(), if (Count > MinCount) -> Count; true -> timer:sleep(100), merge_until(Dir, MinCount, CountSetuids) end. regression_gh82_test_() -> {timeout, 300, ?_assertEqual(ok, regression_gh82_body())}. regression_gh82_body() -> Dir = filename:join(?TEST_FILEPATH, "bc.regression_gh82"), os:cmd("rm -rf " ++ Dir), Reference = bitcask:open(Dir, [read_write | regression_gh82_opts()]), bitcask:put(Reference, <<"key_to_delete">>, <<"tr0ll">>), [ bitcask:put(Reference, term_to_binary(X), <<1:(8 * 1024 * 100)>>) || X <- lists:seq(1, 3000)], bitcask:delete(Reference, <<"key_to_delete">>), [ bitcask:put(Reference, term_to_binary(X), <<1:(8 * 1024 * 100)>>) || X <- lists:seq(1, 3000)], timer:sleep(1000 + 1000), bitcask_merge_worker:merge(Dir, regression_gh82_opts(), {[Dir ++ "/2.bitcask.data"], []}), poll_merge_worker(), timer:sleep(2*1000), bitcask:close(Reference), Reference2 = bitcask:open(Dir, [read_write | regression_gh82_opts()]), not_found = bitcask:get(Reference2, <<"key_to_delete">>), ok. regression_gh82_opts() -> [{max_file_size, 268435456}, {dead_bytes_threshold, 89478485}, {dead_bytes_merge_trigger, 178956970}]. poll_merge_worker() -> case bitcask_merge_worker:status() of {0, undefined} -> ok; _ -> timer:sleep(100), poll_merge_worker() end. change_open_regression_test_() -> {timeout, 300, ?_assertMatch({ok, _}, change_open_regression_body())}. change_open_regression_body() -> Dir = filename:join(?TEST_FILEPATH, "bitcask.qc"), os:cmd("rm -rf " ++ Dir), K1 = <<"K111">>, K2 = <<"K22222">>, K3 = <<"K33">>, K4 = <<"K4444">>, K5 = <<"K55">>, K6 = <<"K6666">>, K6_val1 = <<"K6aaaa">>, K6_val2 = <<"K6b">>, K7 = <<"k">>, %% _V1 = apply(bitcask_qc_fsm,set_keys,[[K1,K2,K3,K4,K5,K6]]), _V2 = apply(?MODULE,testonly__truncate_hint,[10,-14]), _V3 = apply(bitcask,open,[Dir,[read_write,{open_timeout,0},{sync_strategy,none}]]), _V4 = apply(bitcask,delete,[_V3,K3]), _V5 = apply(bitcask,merge,[Dir]), _V6 = apply(bitcask,delete,[_V3,K5]), _V7 = apply(bitcask,get,[_V3,K1]), _V8 = apply(bitcask,put,[_V3,K3,<<>>]), _V9 = apply(bitcask,put,[_V3,K6,K6_val1]), _V10 = apply(bitcask,put,[_V3,K2,<<"x10><">>]), _V30 = apply(bitcask,merge,[Dir]), _V31 = apply(bitcask,put,[_V3,K1,<<>>]), _V32 = apply(bitcask,merge,[Dir]), _V33 = apply(bitcask,put,[_V3,K1,<<"x33><">>]), _V34 = apply(bitcask,put,[_V3,K3,<<"34">>]), _V35 = apply(bitcask,merge,[Dir]), _V36 = apply(bitcask,close,[_V3]), _V37 = apply(?MODULE,testonly__create_stale_lock,[]), _V38 = apply(?MODULE,testonly__create_stale_lock,[]), _V50 = apply(bitcask,open,[Dir,[read_write,{open_timeout,0},{sync_strategy,none}]]), _V51 = apply(bitcask,put,[_V50,K7,<<"x51>>><">>]), _V52 = apply(bitcask,get,[_V50,K6]), _V53 = apply(bitcask,put,[_V50,K2,<<"x53">>]), _V54 = apply(bitcask,merge,[Dir]), _V55 = apply(bitcask,get,[_V50,K3]), _V56 = apply(bitcask,merge,[Dir]), _V57 = apply(bitcask,merge,[Dir]), _V58 = apply(bitcask,merge,[Dir]), _V59 = apply(bitcask,close,[_V50]), _V60 = apply(bitcask,open,[Dir,[read_write,{open_timeout,0},{sync_strategy,none}]]), _V61 = apply(bitcask,merge,[Dir]), _V62 = apply(bitcask,delete,[_V60,K4]), _V63 = apply(bitcask,get,[_V60,K2]), _V64 = apply(bitcask,get,[_V60,K2]), _V65 = apply(bitcask,put,[_V60,K7,<<"x65>><">>]), _V66 = apply(bitcask,merge,[Dir]), _V67 = apply(bitcask,delete,[_V60,K6]), _V68 = apply(bitcask,put,[_V60,K6,K6_val2]), _V69 = apply(bitcask,close,[_V60]), _V70 = apply(bitcask,open,[Dir,[read_write,{open_timeout,0},{sync_strategy,none}]]), _V71 = apply(bitcask,get,[_V70,K6]), case _V71 of {ok, K6_val1} -> {bummer, "Original EQC failure"}; {ok, K6_val2} -> {ok, "this is eqc expected result hooray test passes"}; Else -> {bummer, unexpected_failure, Else} end. new_20131217_a_test_() -> {timeout, 300, ?_assertEqual(ok, new_20131217_a_body())}. %% 37> io:format("~w.\n", [C76]). %% [[{set,{var,1},{call,bitcask_pulse,incr_clock,[]}},{set,{var,2},{call,bitcask_pulse,bc_open,[true]}},{set,{var,3},{call,bitcask_pulse,puts,[{var,2},{1,13},<<0>>]}},{set,{var,10},{call,bitcask_pulse,delete,[{var,2},13]}},{set,{var,14},{call,bitcask_pulse,puts,[{var,2},{1,21},<<0,0,0>>]}},{set,{var,18},{call,bitcask_pulse,puts,[{var,2},{1,15},<<0,0,0>>]}},{set,{var,24},{call,bitcask_pulse,fork_merge,[{var,2}]}},{set,{var,27},{call,bitcask_pulse,bc_close,[{var,2}]}},{set,{var,28},{call,bitcask_pulse,incr_clock,[]}},{set,{var,40},{call,bitcask_pulse,fork,[[{init,{state,undefined,false,false,[]}},{set,{not_var,6},{not_call,bitcask_pulse,bc_open,[false]}},{set,{not_var,17},{not_call,bitcask_pulse,fold,[{not_var,6}]}}]]}}],{99742,1075,90258},[{events,[]}]]. new_20131217_a_body() -> catch token:stop(), TestDir = filename:join(?TEST_FILEPATH, token:get_name()), bitcask_time:test__set_fudge(10), MOD = ?MODULE, MFS = 1000, V1 = <<"v">>, V2 = <<"v22">>, V3 = <<"v33">>, V1017_expected = [{1,<<"v33">>}, {2,<<"v33">>}, {3,<<"v33">>}, {4,<<"v33">>}, {5,<<"v33">>}, {6,<<"v33">>}, {7,<<"v33">>}, {8,<<"v33">>}, {9,<<"v33">>}, {10,<<"v33">>}, {11,<<"v33">>}, {12,<<"v33">>}, {13,<<"v33">>}, {14,<<"v33">>}, {15,<<"v33">>}, {16,<<"v22">>}, {17,<<"v22">>}, {18,<<"v22">>}, {19,<<"v22">>}, {20,<<"v22">>}, {21,<<"v22">>}], _Var1 = erlang:apply(MOD,incr_clock,[]), _Var2 = erlang:apply(MOD,bc_open,[TestDir,MFS]), _Var3 = erlang:apply(MOD,puts,[_Var2,{1,13},V1]), _Var10 = erlang:apply(MOD,delete,[_Var2,13]), not_found = get(_Var2, 13), %not from EQC _Var14 = erlang:apply(MOD,puts,[_Var2,{1,21},V2]), {ok, V2} = get(_Var2, 13), %not from EQC _Var18 = erlang:apply(MOD,puts,[_Var2,{1,15},V3]), {ok, V3} = get(_Var2, 13), %not from EQC timer:sleep(1234), %not from EQC _Var24 = erlang:apply(MOD,fork_merge,[_Var2, TestDir]), timer:sleep(1235), %not from EQC {ok, V3} = get(_Var2, 13), %not from EQC {ok, V3} = get(_Var2, 13), %not from EQC _Var27 = erlang:apply(MOD,bc_close,[_Var2]), _Var28 = erlang:apply(MOD,incr_clock,[]), _Var106 = erlang:apply(MOD,bc_open,[TestDir,MFS]), {ok, V3} = get(_Var106, 13), %not from EQC _Var1017 = erlang:apply(MOD,fold,[_Var106]), {ok, V3} = get(_Var106, 13), %not from EQC bc_close(_Var106), bitcask_time:test__clear_fudge(), ?assertEqual(V1017_expected, lists:sort(_Var1017)), ok. new_20131217_c_test_() -> {timeout, 300, ?_assertEqual(ok, new_20131217_c_body())}. new_20131217_c_body() -> catch token:stop(), TestDir = filename:join(?TEST_FILEPATH, token:get_name()), bitcask_time:test__set_fudge(10), MOD = ?MODULE, MFS = 1000, Val1 = <<"vv">>, Val2 = <<"V">>, _Var15 = erlang:apply(MOD,bc_open,[TestDir,MFS]), _Var17 = erlang:apply(MOD,puts,[_Var15,{1,4},Val1]), _Var21 = erlang:apply(MOD,bc_close,[_Var15]), _Var22 = erlang:apply(MOD,incr_clock,[]), _Var23 = erlang:apply(MOD,bc_open,[TestDir,MFS]), _Var24 = erlang:apply(MOD,puts,[_Var23,{1,3},Val2]), timer:sleep(1234), % Sleeps necessary for 100% determinism, alas _Var25 = erlang:apply(MOD,merge,[_Var23, TestDir]), _Var26 = erlang:apply(MOD,delete,[_Var23,4]), not_found = MOD:get(_Var23,4), _Var27 = erlang:apply(MOD,bc_close,[_Var23]), _Var28 = erlang:apply(MOD,bc_open,[TestDir,MFS]), not_found = MOD:get(_Var28,4), _Var33 = erlang:apply(MOD,fold,[_Var28]), not_found = MOD:get(_Var28,4), bc_close(_Var28), bitcask_time:test__clear_fudge(), Expected33 = [{1,Val2},{2,Val2},{3,Val2}], ?assertEqual(Expected33, lists:sort(_Var33)), os:cmd("rm -rf " ++ TestDir), ok. %% new_20131217_d_test_() -> %% {timeout, 300, ?_assertEqual(ok, new_20131217_d_body())}. %% new_20131217_d_body() -> %% catch token:stop(), %% TestDir = filename:join(?TEST_FILEPATH, token:get_name()), %% MOD = ?MODULE, %% Val1 = <<0,0,0,0,0,0,0,0,0,0>>,%<<"v111111111">>, %% Val2 = <<0,0,0,0,0,0,0,0,0,0,0,0,0>>,%<<"v222222222222">>, %% Val3 = <<0,0,0,0,0>>, %% Val4 = <<0,0>>, %% V67_expected = [], %% bitcask_time:test__set_fudge(10), %% _Var1 = erlang:apply(MOD,bc_open,[TestDir]), %% _Var26 = erlang:apply(MOD,puts,[_Var1,{1,1},Val1]), %% _Var28 = erlang:apply(MOD,bc_close,[_Var1]), %% _Var30 = erlang:apply(MOD,bc_open,[TestDir]), %% _Var32 = erlang:apply(MOD,puts,[_Var30,{1,6},Val2]), %% _Var33 = erlang:apply(MOD,bc_close,[_Var30]), %% _Var36 = erlang:apply(MOD,incr_clock,[]), %% _Var38 = erlang:apply(MOD,bc_open,[TestDir]), %% _Var39 = erlang:apply(MOD,delete,[_Var38,1]), %% %% timer:sleep(1234), %% _Var40 = erlang:apply(MOD,merge,[_Var38, TestDir]), %% %% timer:sleep(1235), %% _Var41 = erlang:apply(MOD,puts,[_Var38,{1,4},Val3]), %% _Var49 = erlang:apply(MOD,puts,[_Var38,{1,2},Val4]), %% _Var50 = erlang:apply(MOD,delete,[_Var38,1]), %% _Var51 = erlang:apply(MOD,delete,[_Var38,2]), %% _Var52 = erlang:apply(MOD,delete,[_Var38,3]), %% _Var53 = erlang:apply(MOD,delete,[_Var38,4]), %% _Var54 = erlang:apply(MOD,delete,[_Var38,5]), %% _Var55 = erlang:apply(MOD,incr_clock,[]), %% _Var56 = erlang:apply(MOD,delete,[_Var38,6]), %% %% timer:sleep(1234), %% _Var61 = erlang:apply(MOD,merge,[_Var38, TestDir]), %% %% timer:sleep(1235), %% _Var62 = erlang:apply(MOD,bc_close,[_Var38]), %% _Var64 = erlang:apply(MOD,bc_open,[TestDir]), %% _Var67 = erlang:apply(MOD,fold,[_Var64]), %% bc_close(_Var64), %% ?assertEqual(V67_expected, lists:sort(_Var67)), %% ok. new_20131217_e_test_() -> {timeout, 300, ?_assertEqual(ok, new_20131217_e_body())}. new_20131217_e_body() -> catch token:stop(), TestDir = filename:join(?TEST_FILEPATH, token:get_name()), bitcask_time:test__set_fudge(10), MOD = ?MODULE, MFS = 400, V1 = <<"v111111111111<">>, %<<0,0,0,0,0,0,0,0,0,0,0,0,0,0>>, V2 = <<"v222222<">>, %<<0,0,0,0,0,0,0,0>>, V3 = <<"v33333333333<">>, %<<0,0,0,0,0,0,0,0,0,0,0,0,0>>, V4 = <<"v4444444<">>, %<<0,0,0,0,0,0,0,0,0>>, V5 = <<"v5<">>, %<<0,0,0>>, V63_expected = [{4,V2}, {5,V2}, {6,V2}, {7,V2}, {8,V2}, {9,V2}, {10,V2}, {11,V1}, {15,V5}, {16,V4}, {17,V4}, {18,V3}, {19,V3}, {20,V3}, {21,V3}, {22,V3}, {23,V3}, {24,V3}, {25,V3}, {26,V3}, {27,V3}, {28,V3}, {29,V3}, {30,V3}, {31,V3}, {32,V3}], _Var2 = erlang:apply(MOD,bc_open,[TestDir,MFS]), _Var4 = erlang:apply(MOD,puts,[_Var2,{1,11},V1]), _Var9 = erlang:apply(MOD,bc_close,[_Var2]), _Var12 = erlang:apply(MOD,bc_open,[TestDir,MFS]), _Var13 = erlang:apply(MOD,incr_clock,[]), _Var16 = erlang:apply(MOD,delete,[_Var12,1]), _Var19 = erlang:apply(MOD,puts,[_Var12,{1,10},V2]), _Var20 = erlang:apply(MOD,delete,[_Var12,1]), _Var22 = erlang:apply(MOD,delete,[_Var12,3]), _Var24 = erlang:apply(MOD,merge_these,[_Var12, TestDir, [1]]), %% _Var24 = erlang:apply(MOD,merge,[_Var12, TestDir]), _Var27 = erlang:apply(MOD,puts,[_Var12,{14,32},V3]), _Var28 = erlang:apply(MOD,delete,[_Var12,14]), _Var37 = erlang:apply(MOD,puts,[_Var12,{15,17},V4]), _Var38 = erlang:apply(MOD,delete,[_Var12,2]), _Var39 = erlang:apply(MOD,incr_clock,[]), _Var45 = erlang:apply(MOD,puts,[_Var12,{15,15},V5]), _Var46 = erlang:apply(MOD,merge_these,[_Var12, TestDir, [1,2,4,5,6]]), %% _Var46 = erlang:apply(MOD,merge,[_Var12, TestDir]), _Var54 = erlang:apply(MOD,bc_close,[_Var12]), _Var56 = erlang:apply(MOD,bc_open,[TestDir,MFS]), _Var63 = erlang:apply(MOD,fold,[_Var56]), bitcask_time:test__clear_fudge(), ?assertEqual(V63_expected, lists:sort(_Var63)), ok. bc_open(Dir, MaxFileSize) -> bitcask:open(Dir, [read_write, {max_file_size, MaxFileSize}, {open_timeout, 1234}]). nice_key(K) -> list_to_binary(io_lib:format("kk~2.2.0w", [K])). un_nice_key(<<"kk", Num:2/binary>>) -> list_to_integer(binary_to_list(Num)). get(H, K) -> bitcask:get(H, nice_key(K)). put(H, K, V) -> ok = bitcask:put(H, nice_key(K), V). puts(H, {K1, K2}, V) -> case lists:usort([ put(H, K, V) || K <- lists:seq(K1, K2) ]) of [ok] -> ok; Other -> Other end. delete(H, K) -> ok = bitcask:delete(H, nice_key(K)). merge(H, TestDir) -> case bitcask:needs_merge(H) of {true, Files} -> case catch bitcask:merge(TestDir, [], Files) of {'EXIT', Err} -> Err; R -> R end; false -> not_needed end. fork_merge(H, Dir) -> case bitcask:needs_merge(H) of {true, Files} -> catch bitcask_merge_worker:merge(Dir, [], Files); false -> not_needed end. merge_these(_H, TestDir, Ids) -> Files = [lists:flatten(io_lib:format("~s/~w.bitcask.data", [TestDir,Id])) || Id <- Ids], bitcask:merge(TestDir, [], Files). incr_clock() -> bitcask_time:test__incr_fudge(1). bc_close(H) -> ok = bitcask:close(H). fold_keys(H) -> bitcask:fold_keys(H, fun(#bitcask_entry{key = KBin}, Ks) -> [un_nice_key(KBin)|Ks] end, []). fold(H) -> bitcask:fold(H, fun(KBin, V, Acc) -> [{un_nice_key(KBin),V}|Acc] end, []). merge_delete_race_pr156_regression_test_() -> {timeout, 60, fun merge_delete_race_pr156_regression_test2/0}. merge_delete_race_pr156_regression_test2() -> %% Heart of the problem: %% 1. delete a key: this creates 1.bitcask.data %% 2. merge. input files is [1.data.bitcask] %% 3. merge defer delete file_id 1. %% 4. Close the cask. %% 5. Open the cask. file_id 1 is marked for deferred deletion, %% so delete it immediately. %% 6. Cask open finishes. The largest file_id is 1. %% 7. Write some data. It is written to file_id 1. %% 8. The merge_delete_worker now deletes file_id 1, but %% it's far too late and deletes a new incarnation of %% file_id 1. Dir = filename:join(?TEST_FILEPATH, "bc.mrg_delete_race_pr156_regression"), os:cmd("rm -rf " ++ Dir), _ = application:start(bitcask), try Ref1 = bitcask:open(Dir, [read_write]), bitcask:delete(Ref1, <<"does_not_exist">>), bitcask_merge_delete ! timeout, timer:sleep(10), ok = bitcask:merge(Dir), ok = bitcask:close(Ref1), Ref2 = bitcask:open(Dir, [read_write]), ok = bitcask:merge(Dir), ok = bitcask:close(Ref2), Ref3 = bitcask:open(Dir, [read_write]), Val = <<"val!">>, NumKeys = 7, [ok = bitcask:put(Ref3, nice_key(K), Val) || K <- lists:seq(1,NumKeys)], bitcask_merge_delete ! timeout, timer:sleep(2000), XX = bitcask:fold(Ref3, fun(KBin, V, Acc) -> [{un_nice_key(KBin),V}|Acc] end, []), ?assertEqual(NumKeys, length(XX)), ok = bitcask:close(Ref3) after ok % application:stop(bitcask) end. -endif. %% TEST bitcask-2.1.0/src/bitcask_file.erl0000644000232200023220000001576013655023466017435 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_file). -behaviour(gen_server). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_side_effect, [{file, '_', '_'}]}). -endif. %% API -export([file_open/2, file_close/1, file_sync/1, file_pread/3, file_read/2, file_pwrite/3, file_write/2, file_position/2, file_seekbof/1, file_truncate/1, file_request/2, check_pid/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, {fd :: file:fd() | undefined, owner :: pid() | undefined}). %%%=================================================================== %%% API %%%=================================================================== file_open(Filename, Opts) -> {ok, Pid} = gen_server:start(?MODULE, [], []), Owner = self(), case gen_server:call(Pid, {file_open, Owner, Filename, Opts}, infinity) of ok -> {ok, Pid}; Error -> Error end. file_close(Pid) -> file_request(Pid, file_close). file_sync(Pid) -> file_request(Pid, file_sync). file_pread(Pid, Offset, Size) -> file_request(Pid, {file_pread, Offset, Size}). file_pwrite(Pid, Offset, Bytes) -> file_request(Pid, {file_pwrite, Offset, Bytes}). file_read(Pid, Size) -> file_request(Pid, {file_read, Size}). file_write(Pid, Bytes) -> file_request(Pid, {file_write, Bytes}). file_position(Pid, Position) -> file_request(Pid, {file_position, Position}). file_seekbof(Pid) -> file_request(Pid, file_seekbof). file_truncate(Pid) -> file_request(Pid, file_truncate). %%%=================================================================== %%% API helper functions %%%=================================================================== file_request(Pid, Request) -> case check_pid(Pid) of ok -> try gen_server:call(Pid, Request, infinity) catch exit:{normal,_} when Request == file_close -> %% Honest race condition in bitcask_eqc PULSE test. ok; exit:{noproc,_} when Request == file_close -> %% Honest race condition in bitcask_eqc PULSE test. ok; X1:X2 -> exit({file_request_error, self(), Request, X1, X2}) end; Error -> Error end. check_pid(Pid) -> IsPid = is_pid(Pid), IsAlive = IsPid andalso is_process_alive(Pid), case {IsAlive, IsPid} of {true, _} -> ok; {false, true} -> %% Same result as `file' module when accessing closed FD {error, einval}; _ -> %% Same result as `file' module when providing wrong arg {error, badarg} end. %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([]) -> {ok, #state{}}. handle_call({file_open, Owner, Filename, Opts}, _From, State) -> monitor(process, Owner), IsCreate = proplists:get_bool(create, Opts), IsReadOnly = proplists:get_bool(readonly, Opts), Mode = case {IsReadOnly, IsCreate} of {true, _} -> [read, raw, binary]; {_, false} -> [read, write, raw, binary]; {_, true} -> [read, write, exclusive, raw, binary] end, _ = [error_logger:warning_msg("Bitcask file option '~p' not supported~n", [Opt]) || Opt <- [o_sync], proplists:get_bool(Opt, Opts)], case file:open(Filename, Mode) of {ok, Fd} -> State2 = State#state{fd=Fd, owner=Owner}, {reply, ok, State2}; Error = {error, Reason} -> error_logger:warning_msg("Failed to open file ~p: ~p~n", [Filename, Reason]), {stop, {file_open_failed, Reason}, Error, State} end; handle_call(file_close, From, State=#state{fd=Fd}) -> check_owner(From, State), ok = file:close(Fd), {stop, normal, ok, State}; handle_call(file_sync, From, State=#state{fd=Fd}) -> check_owner(From, State), Reply = file:sync(Fd), {reply, Reply, State}; handle_call({file_pread, Offset, Size}, From, State=#state{fd=Fd}) -> check_owner(From, State), Reply = file:pread(Fd, Offset, Size), {reply, Reply, State}; handle_call({file_pwrite, Offset, Bytes}, From, State=#state{fd=Fd}) -> check_owner(From, State), Reply = file:pwrite(Fd, Offset, Bytes), {reply, Reply, State}; handle_call({file_read, Size}, From, State=#state{fd=Fd}) -> check_owner(From, State), Reply = file:read(Fd, Size), {reply, Reply, State}; handle_call({file_write, Bytes}, From, State=#state{fd=Fd}) -> check_owner(From, State), Reply = file:write(Fd, Bytes), {reply, Reply, State}; handle_call({file_position, Position}, From, State=#state{fd=Fd}) -> check_owner(From, State), Reply = file:position(Fd, Position), {reply, Reply, State}; handle_call(file_seekbof, From, State=#state{fd=Fd}) -> check_owner(From, State), {ok, _} = file:position(Fd, bof), {reply, ok, State}; handle_call(file_truncate, From, State=#state{fd=Fd}) -> check_owner(From, State), {reply, file:truncate(Fd), State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info({'DOWN', _Ref, _, _Pid, _Status}, State=#state{fd=Fd}) -> %% Owner has stopped, close file and shutdown _ = file:close(Fd), {stop, normal, State}; handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%=================================================================== check_owner({Pid, _Mref}, #state{owner=Owner}) -> case Pid == Owner of true -> ok; false -> throw(owner_invariant_failed), ok end. bitcask-2.1.0/src/bitcask_fileops.erl0000644000232200023220000010064113655023466020150 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- %% @doc Basic file i/o operations for bitcask. -module(bitcask_fileops). -export([create_file/3, open_file/1, open_file/2, close/1, close_all/1, close_for_writing/1, data_file_tstamps/1, write/4, read/3, sync/1, delete/1, fold/3, fold_keys/3, fold_keys/4, mk_filename/2, filename/1, hintfile_name/1, file_tstamp/1, check_write/4, un_write/1]). -export([read_file_info/1, write_file_info/2, is_file/1]). -include_lib("kernel/include/file.hrl"). -include("bitcask.hrl"). -define(HINT_RECORD_SZ, 18). % Tstamp(4) + KeySz(2) + TotalSz(4) + Offset(8) -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_side_effect, [{file, '_', '_'}, {prim_file, '_', '_'}, {bitcask_nifs, '_', '_'}]}). -endif. -ifdef(TEST). -compile([export_all, nowarn_export_all]). -include_lib("eunit/include/eunit.hrl"). -endif. -type filestate() :: #filestate{}. %% @doc Open a new file for writing. %% Called on a Dirname, will open a fresh file in that directory. -spec create_file(Dirname :: string(), Opts :: [any()], reference()) -> {ok, filestate()} | {error, term()}. create_file(DirName, Opts0, Keydir) -> Opts = [create|Opts0], case get_create_lock(DirName) of {ok, Lock} -> try {ok, Newest} = bitcask_nifs:increment_file_id(Keydir), Filename = mk_filename(DirName, Newest), ok = ensure_dir(Filename), %% Check for o_sync strategy and add to opts FinalOpts = case bitcask:get_opt(sync_strategy, Opts) of o_sync -> [o_sync | Opts]; _ -> Opts end, {ok, FD} = bitcask_io:file_open(Filename, FinalOpts), HintFD = open_hint_file(Filename, FinalOpts), {ok, #filestate{mode = read_write, filename = Filename, tstamp = file_tstamp(Filename), hintfd = HintFD, fd = FD, ofs = 0}} catch Error:Reason -> %% if we fail somehow, do we need to nuke any partial %% state? {Error, Reason} after bitcask_lockops:release(Lock) end; Else -> Else end. get_create_lock(DirName) -> get_create_lock(DirName, 100). get_create_lock(_DirName, 0) -> error(lock_failure); get_create_lock(DirName, N) -> timer:sleep(100-N), case bitcask_lockops:acquire(create, DirName) of {ok, Lock} -> {ok, Lock}; {error, locked} -> get_create_lock(DirName, N - 1); {error, _} = Else -> Else end. %% @doc Open an existing file for reading. %% Called with fully-qualified filename. -spec open_file(Filename :: string()) -> {ok, filestate()} | {error, any()}. open_file(Filename) -> open_file(Filename, readonly). open_file(Filename, append) -> case bitcask_io:file_open(Filename, []) of {ok, FD} -> case bitcask_io:file_position(FD, {eof, 0}) of {ok, 0} -> % File was deleted and we just opened a new one, undo. bitcask_io:file_close(FD), _ = file:delete(Filename), {error, enoent}; {ok, Ofs} -> case reopen_hintfile(Filename) of {error, enoent} -> bitcask_io:file_close(FD), {error, enoent}; {undefined, _} -> bitcask_io:file_close(FD), {error, enoent}; {HintFD, HintCRC} -> {ok, #filestate{mode = read_write, filename = Filename, tstamp = file_tstamp(Filename), fd = FD, hintfd = HintFD, hintcrc = HintCRC, ofs = Ofs }} end end; {error, _Reason} = Err -> Err end; open_file(Filename, readonly) -> case bitcask_io:file_open(Filename, [readonly]) of {ok, FD} -> {ok, #filestate{mode = read_only, filename = Filename, tstamp = file_tstamp(Filename), fd = FD, ofs = 0}}; {error, Reason} -> {error, Reason} end. % Re-open hintfile for appending. -spec reopen_hintfile(string() | filestate()) -> {error, enoent} | {HintFD::port() | undefined, CRC :: non_neg_integer()}. reopen_hintfile(Filename) -> case (catch open_hint_file(Filename, [])) of couldnt_open_hintfile -> {undefined, 0}; HintFD -> HintFilename = hintfile_name(Filename), {ok, HintI} = read_file_info(HintFilename), HintSize = HintI#file_info.size, case bitcask_io:file_position(HintFD, HintSize) of {ok, 0} -> bitcask_io:file_close(HintFD), _ = file:delete(HintFilename), {error, enoent}; {ok, _FileSize} -> prepare_hintfile_for_append(HintFD) end end. % Removes the final CRC record so more records can be added to the file. -spec prepare_hintfile_for_append(HintFD :: port()) -> {HintFD :: port() | undefined, CRC :: non_neg_integer()}. prepare_hintfile_for_append(HintFD) -> case bitcask_io:file_position(HintFD, {eof, -?HINT_RECORD_SZ}) of {ok, _} -> case read_crc(HintFD) of error -> bitcask_io:file_close(HintFD), {undefined, 0}; HintCRC -> bitcask_io:file_position(HintFD, {eof, -?HINT_RECORD_SZ}), bitcask_io:file_truncate(HintFD), {HintFD, HintCRC} end; _ -> bitcask_io:file_close(HintFD), {undefined, 0} end. %% @doc Use when done writing a file. (never open for writing again) -spec close(filestate()) -> ok. close(fresh) -> ok; close(undefined) -> ok; close(State = #filestate{ fd = FD }) -> _ = close_hintfile(State), bitcask_io:file_close(FD), ok. %% @doc Use when closing multiple files. (never open for writing again) -spec close_all([filestate()]) -> ok. close_all(FileStates) -> lists:foreach(fun ?MODULE:close/1, FileStates), ok. %% @doc Close a file for writing, but leave it open for reads. -spec close_for_writing(filestate()) -> filestate(). close_for_writing(State = #filestate{ mode = read_write, fd = Fd }) -> S2 = close_hintfile(State), bitcask_io:file_sync(Fd), S2#filestate { mode = read_only }. close_hintfile(State = #filestate { hintfd = undefined }) -> State; close_hintfile(State = #filestate { hintfd = HintFd, hintcrc = HintCRC }) -> %% Write out CRC check at end of hint file. Write with an empty key, zero %% timestamp and offset as large as the file format supports so opening with %% an older version of bitcask will just reject the record at the end of the %% hintfile and otherwise work normally. Iolist = hintfile_entry(<<>>, 0, 0, ?MAXOFFSET_V2, HintCRC), _ = bitcask_io:file_write(HintFd, Iolist), _ = bitcask_io:file_sync(HintFd), _ = bitcask_io:file_close(HintFd), State#filestate { hintfd = undefined, hintcrc = 0 }. %% Build a list of {tstamp, filename} for all files in the directory that %% match our regex. -spec data_file_tstamps(Dirname :: string()) -> [{integer(), string()}]. data_file_tstamps(Dirname) -> case list_dir(Dirname) of {ok, Files} -> lists:foldl( fun(Filename, Acc) -> case string:tokens(Filename, ".") of [TSString, _, "data"] -> [{list_to_integer(TSString), filename:join(Dirname, Filename)} | Acc]; _ -> Acc end end, [], Files) end. %% @doc Use only after merging, to permanently delete a data file. -spec delete(string()) -> ok | {error, atom()}. delete(FN) -> _ = file:delete(FN), case has_hintfile(FN) of true -> file:delete(hintfile_name(FN)); false -> ok end. %% @doc Write a Key-named binary data field ("Value") to the Filestate. -spec write(filestate(), Key :: binary(), Value :: binary(), Tstamp :: integer()) -> {ok,filestate(), Offset :: integer(), Size :: integer()} | {error, read_only}. write(#filestate { mode = read_only }, _K, _V, _Tstamp) -> {error, read_only}; write(Filestate=#filestate{fd = FD, hintfd = HintFD, hintcrc = HintCRC0, ofs = Offset}, Key, Value, Tstamp) -> KeySz = size(Key), true = (KeySz =< ?MAXKEYSIZE), ValueSz = size(Value), true = (ValueSz =< ?MAXVALSIZE), %% Setup io_list for writing -- avoid merging binaries if we can help it Bytes0 = [<>, <>, <>, Key, Value], Bytes = [<<(erlang:crc32(Bytes0)):?CRCSIZEFIELD>> | Bytes0], %% Store the full entry in the data file try ok = bitcask_io:file_pwrite(FD, Offset, Bytes), %% Create and store the corresponding hint entry TotalSz = iolist_size(Bytes), TombInt = case bitcask:is_tombstone(Value) of true -> 1; false -> 0 end, Iolist = hintfile_entry(Key, Tstamp, TombInt, Offset, TotalSz), case HintFD of undefined -> ok; _ -> ok = bitcask_io:file_write(HintFD, Iolist) end, %% Record our final offset HintCRC = erlang:crc32(HintCRC0, Iolist), % compute crc of hint {ok, Filestate#filestate{ofs = Offset + TotalSz, hintcrc = HintCRC, l_ofs = Offset, l_hbytes = iolist_size(Iolist), l_hintcrc = HintCRC0}, Offset, TotalSz} catch error:{badmatch,Error} -> Error end. %% WARNING: We can only undo the last write. un_write(Filestate=#filestate{fd = FD, hintfd = HintFD, l_ofs = LastOffset, l_hbytes = LastHintBytes, l_hintcrc = LastHintCRC}) -> {ok, _O2} = bitcask_io:file_position(FD, LastOffset), ok = bitcask_io:file_truncate(FD), {ok, 0} = bitcask_io:file_position(FD, 0), {ok, _HO2} = bitcask_io:file_position(HintFD, {cur, -LastHintBytes}), ok = bitcask_io:file_truncate(HintFD), {ok, Filestate#filestate{ofs = LastOffset, hintcrc = LastHintCRC}}. %% @doc Given an Offset and Size, get the corresponding k/v from Filename. -spec read(Filename :: string() | filestate(), Offset :: integer(), Size :: integer()) -> {ok, Key :: binary(), Value :: binary()} | {error, bad_crc} | {error, atom()}. read(Filename, Offset, Size) when is_list(Filename) -> case open_file(Filename) of {ok, Fstate} -> read(Fstate, Offset, Size); {error, Reason} -> {error, Reason} end; read(#filestate { fd = FD }, Offset, Size) -> case bitcask_io:file_pread(FD, Offset, Size) of {ok, <>} -> %% Verify the CRC of the data case erlang:crc32(Bytes) of Crc32 -> %% Unpack the actual data <<_Tstamp:?TSTAMPFIELD, KeySz:?KEYSIZEFIELD, ValueSz:?VALSIZEFIELD, Key:KeySz/bytes, Value:ValueSz/bytes>> = Bytes, {ok, Key, Value}; _BadCrc -> {error, bad_crc} end; eof -> {error, eof}; {error, Reason} -> {error, Reason} end. %% @doc Call the OS's fsync(2) system call on the cask and hint files. -spec sync(#filestate{}) -> ok. sync(#filestate { mode = read_write, fd = Fd, hintfd = HintFd }) -> ok = bitcask_io:file_sync(Fd), ok = bitcask_io:file_sync(HintFd). -spec fold(fresh | #filestate{}, fun((binary(), binary(), integer(), {list(), integer(), integer(), integer()}, any()) -> any()), any()) -> any() | {error, any()}. fold(fresh, _Fun, Acc) -> Acc; fold(#filestate { fd=Fd, filename=Filename, tstamp=FTStamp }, Fun, Acc0) -> %% TODO: Add some sort of check that this is a read-only file ok = bitcask_io:file_seekbof(Fd), case fold_file_loop(Fd, regular, fun fold_int_loop/5, Fun, Acc0, {Filename, FTStamp, 0, 0}) of {error, Reason} -> {error, Reason}; Acc -> Acc end. -type key_fold_fun() :: fun((binary(), integer(), {integer(), integer()}, any()) -> any()). -type key_fold_mode() :: datafile | hintfile | default | recovery. -spec fold_keys(filestate(), key_fold_fun(), any()) -> any() | {error, any()}. fold_keys(fresh, _Fun, Acc) -> Acc; fold_keys(State, Fun, Acc) -> fold_keys(State, Fun, Acc, default). -spec fold_keys(filestate(), key_fold_fun(), any(), key_fold_mode()) -> any() | {error, any()}. fold_keys(State, Fun, Acc, datafile) -> fold_keys_loop(State, 0, Fun, Acc); fold_keys(#filestate { fd = _Fd } = State, Fun, Acc, hintfile) -> fold_hintfile(State, Fun, Acc); fold_keys(State, Fun, Acc, Mode) -> fold_keys(State, Fun, Acc, Mode, has_hintfile(State)). fold_keys(State, Fun, Acc, default, true) -> fold_hintfile(State, Fun, Acc); fold_keys(State, Fun, Acc, default, false) -> fold_keys_loop(State, 0, Fun, Acc); fold_keys(State, Fun, Acc, recovery, true) -> fold_keys(State, Fun, Acc, recovery, true, has_valid_hintfile(State)); fold_keys(State, Fun, Acc, recovery, false) -> fold_keys_loop(State, 0, Fun, Acc). fold_keys(State, Fun, Acc, recovery, _, true) -> case fold_hintfile(State, Fun, Acc) of {error, {trunc_hintfile, Acc0}} -> Acc0; {error, Reason} -> HintFile = hintfile_name(State), error_logger:warning_msg("Hintfile '~s' failed fold: ~p\n", [HintFile, Reason]), fold_keys_loop(State, 0, Fun, Acc); Acc1 -> Acc1 end; fold_keys(State, Fun, Acc, recovery, _, false) -> HintFile = hintfile_name(State), error_logger:warning_msg("Hintfile '~s' invalid\n", [HintFile]), fold_keys_loop(State, 0, Fun, Acc). -spec mk_filename(string(), integer()) -> string(). mk_filename(Dirname, Tstamp) -> filename:join(Dirname, lists:concat([integer_to_list(Tstamp),".bitcask.data"])). -spec filename(filestate()) -> string(). filename(#filestate { filename = Fname }) -> Fname. -spec hintfile_name(string() | filestate()) -> string(). hintfile_name(Filename) when is_list(Filename) -> filename:rootname(Filename, ".data") ++ ".hint"; hintfile_name(#filestate { filename = Fname }) -> hintfile_name(Fname). -spec file_tstamp(filestate() | string()) -> integer(). file_tstamp(#filestate{tstamp=Tstamp}) -> Tstamp; file_tstamp(Filename) when is_list(Filename) -> list_to_integer(filename:basename(Filename, ".bitcask.data")). -spec check_write(filestate(), binary(), non_neg_integer(), integer()) -> fresh | wrap | ok. check_write(fresh, _Key, _ValSize, _MaxSize) -> %% for the very first write, special-case fresh; check_write(#filestate { ofs = Offset }, Key, ValSize, MaxSize) -> Size = ?HEADER_SIZE + size(Key) + ValSize, case (Offset + Size) > MaxSize of true -> wrap; false -> ok end. -spec has_hintfile(string() | filestate()) -> boolean(). has_hintfile(Filename) -> is_file(hintfile_name(Filename)). %% Return true if there is a hintfile and it has %% a valid CRC check has_valid_hintfile(State) -> HintFile = hintfile_name(State), case bitcask_io:file_open(HintFile, [readonly, read_ahead]) of {ok, HintFd} -> try {ok, HintI} = read_file_info(HintFile), HintSize = HintI#file_info.size, hintfile_validate_loop(HintFd, 0, HintSize) after bitcask_io:file_close(HintFd) end; _ -> false end. hintfile_validate_loop(Fd, CRC0, Rem) -> {ReadLen, HasCRC} = case Rem =< ?CHUNK_SIZE of true -> case Rem < ?HINT_RECORD_SZ of true -> {0, error}; false -> {Rem - ?HINT_RECORD_SZ, true} end; false -> {?CHUNK_SIZE, false} end, case bitcask_io:file_read(Fd, ReadLen) of {ok, Bytes} -> case HasCRC of true -> ExpectCRC = read_crc(Fd), CRC = erlang:crc32(CRC0, Bytes), ExpectCRC =:= CRC; false -> hintfile_validate_loop(Fd, erlang:crc32(CRC0, Bytes), Rem - ReadLen); error -> false end; _ -> false end. read_crc(Fd) -> case bitcask_io:file_read(Fd, ?HINT_RECORD_SZ) of {ok, <<0:?TSTAMPFIELD, 0:?KEYSIZEFIELD, ExpectCRC:?TOTALSIZEFIELD, _TombInt:?TOMBSTONEFIELD_V2, (?MAXOFFSET_V2):?OFFSETFIELD_V2>>} -> ExpectCRC; _ -> error end. %% =================================================================== %% Internal functions %% =================================================================== fold_int_loop(_Bytes, _Fun, Acc, _Consumed, {Filename, _, Offset, 20}) -> error_logger:error_msg("fold_loop: CRC error limit at file ~p offset ~p\n", [Filename, Offset]), {done, Acc}; fold_int_loop(<>, Fun, Acc0, Consumed0, {Filename, FTStamp, Offset, CrcSkipCount}) -> TotalSz = KeySz + ValueSz + ?HEADER_SIZE, case erlang:crc32([<>, Key, Value]) of Crc32 -> PosInfo = {Filename, FTStamp, Offset, TotalSz}, Acc = Fun(Key, Value, Tstamp, PosInfo, Acc0), fold_int_loop(Rest, Fun, Acc, Consumed0 + TotalSz, {Filename, FTStamp, Offset + TotalSz, CrcSkipCount}); _ -> error_logger:error_msg("fold_loop: CRC error at file ~s offset ~p, " "skipping ~p bytes\n", [Filename, Offset, TotalSz]), fold_int_loop(Rest, Fun, Acc0, Consumed0 + TotalSz, {Filename, FTStamp, Offset + TotalSz, CrcSkipCount + 1}) end; fold_int_loop(_Bytes, _Fun, Acc, Consumed, Args) -> {more, Acc, Consumed, Args}. fold_keys_loop(#filestate{fd=Fd, filename=Filename, tstamp=FTStamp}, Offset, Fun, Acc0) -> case bitcask_io:file_position(Fd, Offset) of {ok, Offset} -> ok; Other -> error(Other) end, case fold_file_loop(Fd, regular, fun fold_keys_int_loop/5, Fun, Acc0, {Filename, FTStamp, Offset, 0}) of {error, Reason} -> {error, Reason}; Acc -> Acc end. fold_keys_int_loop(_Bytes, _Fun, Acc, _Consumed, {Filename, _, Offset, 20}) -> error_logger:error_msg("fold_loop: CRC error limit at file ~p offset ~p\n", [Filename, Offset]), {done, Acc}; fold_keys_int_loop(<>, Fun, Acc0, Consumed0, {Filename, FTStamp, Offset, CrcSkipCount}) -> TotalSz = KeySz + ValueSz + ?HEADER_SIZE, case erlang:crc32([<>, Key, Value]) of Crc32 -> PosInfo = {Offset, TotalSz}, KeyPlus = case bitcask:is_tombstone(Value) of true -> {tombstone, Key}; false -> Key end, Acc = Fun(KeyPlus, Tstamp, PosInfo, Acc0), fold_keys_int_loop(Rest, Fun, Acc, Consumed0 + TotalSz, {Filename, FTStamp, Offset + TotalSz, CrcSkipCount}); _ -> error_logger:error_msg("fold_loop: CRC error at file ~s offset ~p, " "skipping ~p bytes\n", [Filename, Offset, TotalSz]), fold_keys_int_loop(Rest, Fun, Acc0, Consumed0 + TotalSz, {Filename, FTStamp, Offset + TotalSz, CrcSkipCount + 1}) end; fold_keys_int_loop(_Bytes, _Fun, Acc, Consumed, Args) -> {more, Acc, Consumed, Args}. fold_hintfile(State, Fun, Acc0) -> HintFile = hintfile_name(State), case bitcask_io:file_open(HintFile, [readonly, read_ahead]) of {ok, HintFd} -> try {ok, DataI} = read_file_info(State#filestate.filename), DataSize = DataI#file_info.size, case fold_file_loop(HintFd, hint, fun fold_hintfile_loop/5, Fun, Acc0, {DataSize, HintFile}) of {error, Reason} -> {error, Reason}; Acc -> Acc end after bitcask_io:file_close(HintFd) end; {error, Reason} -> {error, {fold_hintfile, Reason}} end. %% conditional end match here, checking that we get the expected CRC-containing %% hint record, three-tuple done indicates that we've exhausted all bytes, or %% it's an error fold_hintfile_loop(<<0:?TSTAMPFIELD, 0:?KEYSIZEFIELD, _ExpectCRC:?TOTALSIZEFIELD, _TombInt:?TOMBSTONEFIELD_V2, (?MAXOFFSET_V2):?OFFSETFIELD_V2>>, _Fun, Acc, Consumed, _Args) -> {done, Acc, Consumed + ?HINT_RECORD_SZ}; %% main work loop here, containing the full match of hint record and key. %% if it gets a match, it proceeds to recurse over the rest of the big %% binary fold_hintfile_loop(<>, Fun, Acc0, Consumed0, {DataSize, HintFile} = Args) -> case Offset + TotalSz =< DataSize + 1 of true -> PosInfo = {Offset, TotalSz}, KeyPlus = if TombInt == 1 -> {tombstone, Key}; true -> Key end, Acc = Fun(KeyPlus, Tstamp, PosInfo, Acc0), Consumed = KeySz + ?HINT_RECORD_SZ + Consumed0, fold_hintfile_loop(Rest, Fun, Acc, Consumed, Args); false -> error_logger:warning_msg("Hintfile '~s' contains pointer ~p ~p " "that is greater than total data size ~p\n", [HintFile, Offset, TotalSz, DataSize]), {error, {trunc_hintfile, Acc0}} end; %% catchall case where we don't get enough bytes from fold_file_loop fold_hintfile_loop(_Bytes, _Fun, Acc0, Consumed0, Args) -> {more, Acc0, Consumed0, Args}. %% @doc scaffolding for faster folds over large files. %% The somewhat tricky thing here is the FoldFn, which is a /6 %% that does all the actual work. see fold_hintfile_loop as a %% commented example -spec fold_file_loop(port(), atom(), fun((binary(), fun(), any(), integer(), any()) -> {more, any(), integer(), any()} | {done, any()} | {done, any(), integer()} | {skip, any(), integer(), any()} | {error, any()}), fun(), any(), any()) -> {error, any()} | any(). fold_file_loop(Fd, Type, FoldFn, IntFoldFn, Acc, Args) -> fold_file_loop(Fd, Type, FoldFn, IntFoldFn, Acc, Args, none, ?CHUNK_SIZE). fold_file_loop(Fd, Type, FoldFn, IntFoldFn, Acc0, Args0, Prev0, ChunkSz0) -> %% analyze what happened in the last loop to determine whether or %% not to change the read size. This is an optimization for large values %% in datafile folds and key folds {Prev, ChunkSz} = case Prev0 of none -> {<<>>, ChunkSz0}; Other -> CS = case byte_size(Other) of %% to avoid having to rescan the same %% binaries over and over again. N when N >= ?MAX_CHUNK_SIZE -> ?MAX_CHUNK_SIZE; N when N > ChunkSz0 -> ChunkSz0 * 2; _ -> ChunkSz0 end, {Other, CS} end, case bitcask_io:file_read(Fd, ChunkSz) of {ok, <>} -> Bytes = <>, case FoldFn(Bytes, IntFoldFn, Acc0, 0, Args0) of %% foldfuns should return more when they don't have enough %% bytes to satisfy their main binary match. {more, Acc, Consumed, Args} -> Rest = case Consumed > byte_size(Bytes) of true -> <<>>; false -> <<_:Consumed/bytes, R/binary>> = Bytes, R end, fold_file_loop(Fd, Type, FoldFn, IntFoldFn, Acc, Args, Rest, ChunkSz); %% the done two tuple is returned when we want to be %% unconditionally successfully finished, %% i.e. trailing data is a non-fatal error {done, Acc} -> Acc; %% three tuple done requires full consumption of all %% bytes given to the internal fold function, to %% satisfy the pre-existing semantics of hintfile %% folds. {done, Acc, Consumed} -> case Consumed =:= byte_size(Bytes) of true -> Acc; false -> {error, {partial_fold, Consumed, Bytes, Bytes0}} end; {error, Reason} -> {error, Reason} end; eof -> %% when we reach the end of the file, if it's a hintfile, %% we need to make sure that require_hint_crc is honored %% (or not as the case may be). case Prev == <<>> andalso Type == hint of false -> Acc0; true -> case application:get_env(bitcask, require_hint_crc) of {ok, true} -> {error, {incomplete_hint, 4}}; _ -> Acc0 end end; {error, Reason} -> {error, Reason} end. open_hint_file(Filename, FinalOpts) -> open_hint_file(Filename, FinalOpts, 10). open_hint_file(_Filename, _FinalOpts, 0) -> throw(couldnt_open_hintfile); open_hint_file(Filename, FinalOpts, Count) -> case bitcask_io:file_open(hintfile_name(Filename), FinalOpts) of {ok, FD} -> FD; {error, eexist} -> timer:sleep(50), open_hint_file(Filename, FinalOpts, Count - 1) end. hintfile_entry(Key, Tstamp, TombInt, Offset, TotalSz) -> KeySz = size(Key), [<>, Key]. %% =================================================================== %% file/filelib avoidance code. %% =================================================================== read_file_info(FileName) -> prim_file:read_file_info(FileName). write_file_info(FileName, Info) -> prim_file:write_file_info(FileName, Info). is_file(File) -> case read_file_info(File) of {ok, #file_info{type=regular}} -> true; {ok, #file_info{type=directory}} -> true; _ -> false end. is_dir(File) -> case read_file_info(File) of {ok, #file_info{type=directory}} -> true; _ -> false end. ensure_dir("/") -> ok; ensure_dir(F) -> Dir = filename:dirname(F), case is_dir(Dir) of true -> ok; false when Dir =:= F -> %% Protect against infinite loop {error,einval}; false -> _ = ensure_dir(Dir), %% this is rare enough that the serialization %% isn't super important, maybe case file:make_dir(Dir) of {error,eexist}=EExist -> case is_dir(Dir) of true -> ok; false -> EExist end; Err -> Err end end. -ifdef(dirty_file_nif). list_dir(Dir) -> file:list_dir(Dir). -else. list_dir(Dir) -> list_dir(Dir, 1). list_dir(_, 0) -> {error, efile_driver_unavailable}; list_dir(Directory, Retries) when is_integer(Retries), Retries > 0 -> Port = get_efile_port(), case prim_file:list_dir(Port, Directory) of {error, einval} -> clear_efile_port(), list_dir(Directory, Retries-1); Result -> Result end. get_efile_port() -> Key = bitcask_efile_port, case get(Key) of undefined -> case prim_file_drv_open("efile", [binary]) of {ok, Port} -> put(Key, Port), get_efile_port(); Err -> error_logger:error_msg("get_efile_port: ~p\n", [Err]), timer:sleep(1000), get_efile_port() end; Port -> Port end. clear_efile_port() -> erase(bitcask_efile_port). prim_file_drv_open(Driver, Portopts) -> try erlang:open_port({spawn, Driver}, Portopts) of Port -> {ok, Port} catch error:Reason -> {error, Reason} end. -endif. bitcask-2.1.0/src/bitcask_io.erl0000644000232200023220000001027713655023466017123 0ustar debalancedebalance%% ------------------------------------------------------------------- %% %% bitcask: Eric Brewer-inspired key/value store %% %% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- -module(bitcask_io). -export([file_open/2, file_close/1, file_sync/1, file_read/2, file_pread/3, file_write/2, file_pwrite/3, file_seekbof/1, file_position/2, file_truncate/1]). -ifdef(PULSE). -compile({parse_transform, pulse_instrument}). -include_lib("pulse_otp/include/pulse_otp.hrl"). -compile({pulse_side_effect, [{file, '_', '_'}]}). -endif. -ifdef(TEST). -export([determine_file_module/0]). -include_lib("eunit/include/eunit.hrl"). -include("bitcask.hrl"). -endif. file_open(Filename, Opts) -> M = file_module(), M:file_open(Filename, Opts). file_close(Ref) -> M = file_module(), M:file_close(Ref). file_sync(Ref) -> M = file_module(), M:file_sync(Ref). file_pread(Ref, Offset, Size) -> M = file_module(), M:file_pread(Ref, Offset, Size). file_pwrite(Ref, Offset, Bytes) -> M = file_module(), M:file_pwrite(Ref, Offset, Bytes). file_read(Ref, Size) -> M = file_module(), M:file_read(Ref, Size). file_write(Ref, Bytes) -> M = file_module(), M:file_write(Ref, Bytes). file_seekbof(Ref) -> M = file_module(), M:file_seekbof(Ref). file_position(Ref, Position) -> M = file_module(), M:file_position(Ref, Position). file_truncate(Ref) -> M = file_module(), M:file_truncate(Ref). file_module() -> case get(bitcask_file_mod) of undefined -> Mod = determine_file_module(), put(bitcask_file_mod, Mod), Mod; Mod -> Mod end. -ifdef(TEST). determine_file_module() -> case application:get_env(bitcask, io_mode) of {ok, erlang} -> bitcask_file; {ok, nif} -> bitcask_nifs; _ -> Mode = case os:getenv("BITCASK_IO_MODE") of false -> 'erlang'; "erlang" -> 'erlang'; "nif" -> 'nif' end, application:set_env(bitcask, io_mode, Mode), determine_file_module() end. -else. determine_file_module() -> case application:get_env(bitcask, io_mode) of {ok, erlang} -> bitcask_file; {ok, nif} -> bitcask_nifs; _ -> bitcask_file end. -endif. -ifdef(TEST). truncate_test_() -> {timeout, 60, fun truncate_test2/0}. truncate_test2() -> Dir = filename:join(?TEST_FILEPATH, "bc.test.bitcask_io/"), one_truncate(filename:join(Dir, "truncate_test1.dat"), 50, 50), one_truncate(filename:join(Dir, "truncate_test2.dat"), {bof, 50}, 50), one_truncate(filename:join(Dir, "truncate_test3.dat"), {cur, -25}, 75), one_truncate(filename:join(Dir, "truncate_test4.dat"), {eof, -75}, 25). one_truncate(Fname, Ofs, ExpectedSize) -> ?assertMatch(ok, filelib:ensure_dir(Fname)), file:delete(Fname), Open1 = file_open(Fname, [create]), ?assertMatch({ok, _}, Open1), {ok, File} = Open1, % Write 100 bytes Bytes = <<0:100/integer-unit:8>>, ?assertEqual(100, size(Bytes)), ok = file_write(File, Bytes), ?assertEqual({Ofs, {ok, ExpectedSize}}, {Ofs, file_position(File, Ofs)}), ok = file_truncate(File), ok = file_close(File), % Verify size with regular file operations {ok, File3} = file:open(Fname, [read, raw, binary]), SizeRes = file:position(File3, {eof, 0}), ok = file:close(File3), ?assertEqual({ok, ExpectedSize}, SizeRes). -endif.