From a21c418ea7d85d230b69945351cf2e57d1849295 Mon Sep 17 00:00:00 2001 From: Matt Peterson Date: Tue, 2 Jul 2024 13:16:06 -0600 Subject: [PATCH] feature: design document for streaming blockitems from producer to consumers Signed-off-by: Matt Peterson --- .../assets/00036-consumer-registration.png | Bin 0 -> 25856 bytes .../00036-demo-disruptor-class-diagram.png | Bin 0 -> 35349 bytes .../assets/00036-producer-registration.png | Bin 0 -> 20439 bytes .../assets/00036-refactor-demo-disruptor.png | Bin 0 -> 43797 bytes .../bidi-producer-consumers-streaming.md | 339 ++++++++++++++++++ 5 files changed, 339 insertions(+) create mode 100644 server/docs/design/assets/00036-consumer-registration.png create mode 100644 server/docs/design/assets/00036-demo-disruptor-class-diagram.png create mode 100644 server/docs/design/assets/00036-producer-registration.png create mode 100644 server/docs/design/assets/00036-refactor-demo-disruptor.png create mode 100644 server/docs/design/bidi-producer-consumers-streaming.md diff --git a/server/docs/design/assets/00036-consumer-registration.png b/server/docs/design/assets/00036-consumer-registration.png new file mode 100644 index 0000000000000000000000000000000000000000..8e29d296a120cfdd9631753a82b0cad528603554 GIT binary patch literal 25856 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 YiMa*1^|0Lt$^(Ugr>mdKI;Vst0KkP|_W%F@ literal 0 HcmV?d00001 diff --git a/server/docs/design/assets/00036-demo-disruptor-class-diagram.png b/server/docs/design/assets/00036-demo-disruptor-class-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..ad6684820b35844462f041df6d5e37cf80e2322c GIT binary patch literal 35349 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 msKfEUIRtzAdjTKT4hi{~v(D$d@x_x}K?3{n>W literal 0 HcmV?d00001 diff --git a/server/docs/design/assets/00036-refactor-demo-disruptor.png b/server/docs/design/assets/00036-refactor-demo-disruptor.png new file mode 100644 index 0000000000000000000000000000000000000000..59755fc27bab452fe1526cbfa9ff1f09b7259e6e GIT binary patch literal 43797 zcmeFZcTm%57e9(?!3J5^RaAu7R;-{>f&_?)%DO7pSwx74fCwbi1PCN5YE%SNRD@7O zMMQcH5Q-Q9l`arU8lfaW8W2b!?S9qWch`O0-~H##ow;{jXB@@|PdW8-KIc5#K4xdD zwQ$u!4Gj&gL%;4np`o!5tf4U*F@HAj3G`4H3iz_d?bj1WH8jFDYG_=!uAwmld~{_* zLnC;HhQ`==4GoJY8X7u&NfpN}H8f_Sj~zL6K&@6!PmABwl8cIoy}i`tmR7l3J~TAU z=L-UZ!!~U*cD?9j2|M`kapLpzSJ&}Tb|)N|OlEp|`qb1Eu=t7U|IyP>>r7be)T$+koufPV}wLRZZ$MUgF zb|d4iF3Qeb#@h^yckVRC;c)5cFO7B>(`fV~C(b6NJjYzQnOjie;T@2imcDI=dCeQL zkAFyIb?w>no}0Iu)Hi&HdGKV1snwAaP9_%n42_{@o!t)pc0Bt2V`sO^8`Rfo0P)Sb zaM*Ug#;v(}zXJbQ;POY8=|X z_f$w9UwJ!q@XX`V+sAb`+&FRK;2gKuyQ`_YFAJbvPGeg#PYoZ>>~was$8~iS&FE4G z6H2YbA&O85SV+>?u-@1&vXXOc&VXes_Rht78n;CCyDg23u0)+#1nl|$;XlO_?G34& z59~oXWY30}viDCXM>-dCTRqXGf)#eZ>Xg{=5~jIx{!zWA%KK zWxVGfr}Ml!%%3>6ZQLUsKIAJa?91(1p;!I7KNijji9LM7QPP{`rs?o8D{t$}stZzf zPp#fHWR^02W?;Ht_O{_JVpn?1+x-v5XX1XF&I{}?zvXz$;Em2px2l=Km~i)f*x((J z(i;&ZH+=XWy)ii&X86d1Zs0MP7pupL`<*!xeBd~#wKFe9&$Z-tk@)l@!_lHXiuZ6D zQaUqi@L)doC+nKU`>*$H!le+a+$gQ_gRalNEDe53wIWJXt^#pm3ukWtZRfJ>CiJj$MtKV%KS=wK;MYOq&p*PYx;Z*{!ydHkP65TBqV$W@9tjN=r z1RMO&5wW$Gl)Y=v(de!`Pq5(?($1xOnsA^oIto0X$Q!#^24CFEjmz6Y4XGm3&uY46 zJ*(Qf2~s1aXBkq#6U}LiO(=;=2K{`qTF{XGEW1&=dp-hZY@0h8q@JATs^hm zsnLa?OZzGt=Hj0GXzHtn`+fW6pAOAY<<}yQk(3=eJ1tDsG6t`~j@(Ktje%t^H@&^# z2V74-@mx;KUbqDbq-(k*;;};z7piJPqo^8CpzaONlm{&rDMJ#!4vvk19 zP)ff3%8M%{Up^l9>$-8g7B;evpIUvZJ#E2+zK+6mUuSW!!nNepp$$u<K7M!YUEE@l$OGrUS|5(dWg2a6AHDEJj}y>9WJEUq`RIn0SZ2xnOuhvuB z==4DlXu;p(-K|8q4RUsizUhYsttlnGO8_@T`cbjwjYodg;X~y6AG^ms?s2*vCM8pJ z-JD%9p>YS*zR*C~sk&Buq@ZIrzF};~G z;lBPoITQ1+s$8@#xrCVnFBLNMJ=EIeRq=DbIf;dwyj}O6{5nA{9?tZB%Bep@b0%#J z4GZk8&fXh$7aN(K9Q%}h&ILwSwE`gBsP(Lq#|H(@ef9pwE0JEH!=BxsgIS`gZ6|XS ztQ*Qemle+y=c7MGFJOxY2JXX}?iz0N5OEL4KkmofPslJzih2zkBHbppz7$qzyo5od za=dXNf;pl2yH?yka;MmL3avww=18Ujj~F26DDK`l*i9P-i@yCk4oNc)rW~rq)@>ryM4w|iFs$Y>-e!i zNZ+RVwW+(IzW5|&--z+nZdFFkz&{mRAtP%tH-L}CZS#54JA><9ShPs;-sOD&v$_Dg z38N0L8x!XN=18>`j%SK-!vob-*dfr-9!IMFz3O1q$Gqu{aPF@^>_3+p;AM&P5UX?D z)pt<&Q=JN5B~vtZA699kswWC+qYL6aTWn94;P9W;I=D4w`rj1nsJNX!j@R95wk~7^LMzgpRQm5{zWaE#W0X=3@yh7@fF8d>p%s#N0qe^*uJzu;6(K55bhCX zDXiEP*260DhExW1^ig-|(ZOd0yq(jEw^4^7t3nW!1L?-)v8}MmZS@O{L_e3)1Q%uVCq-x6qwL3Vzs&o`%3-yUL>VS>4E@#_165;c2`lpSs=EE_v~dHM;l5 z@y#0FNHto?>RPXxzk4>nMbEw@mQkGNat+pG*r%H0>JBbEOjQTVT^qMmaX?1hI-5;; z!Hk}hON@0HRPkwwMTuu3qo=luP)fQknWQN~cKr54r!`N&sQw)n(uXxix)VX&-ng^k~4bZ^-j{fQoTs8=@^he0v8PyiFyw?va=|;Aty9gq}%j;ZT!-@^oB+fS~WfON^qm>8)R^I&;v{IG7_A74et_AmTHIc0? zw2=e7p8RIYE~j(Mgjk6j{|e*S5AwE&R=Of)>zj$FwBPgEN+UPrKnIu6l6wI;`OvA0 zObz?WIr67UI1wyB@=bQ+Xs&-#r^KsvNbKZ}FWuzi0gpR5;cQ#EP+3<6YaE|p37V`h zff#>+tQ&AyQJnyRpKck5F+ZT@KFec!UvtW;Q)#nRJLY@| zd|`SDSN{ZBq^#4*3ez{1yu8s;-=*?$YO;r_8Wg-RA`-yI$Bn-%bRe$S?2Y499&bRv z%c&VKMLuAx<9YvlL6Y|x5`sZiBdR`leo*BrYs$mQ%EAVhO?Z5$3Bi;*3RD9D_KiV4 zxR8)B9*B@_Lm&JTDVL1nu{ZlL5c%&5G7xE8lw922p#$>An{CnlbLWs>$A&=gTN-ko zg5#YZ=FAGpxPievq4zEbRc>3v<^cX3aS2}W0diJ+eKOf%sVd*B z^CMXQafOtuv*F4&;D36R%mx~K(BGl^q;Pp2Rl+@dKxo(6BIwhBIH}t5##(OFlu*Xm z18--@BsxNI2RPq%>x8iFiZs&fu}D&MmIN=h(8Pr>}jF3cdljsisZ7W*Qrlo;mIpZ|8#9(PQ4e0;pb({q-(#7&s5YXKE4^?8_G zSiOI8%15&%zG}CzQzf?2!&^V4IFSEvqi|f7%js0QmKtpZV)p8p-+!ye<<#=j3{j^Pt5RiU% z3-@`2bbo#cKZR$L8ggQVp}0$rtHLe`8Ap+l_jlq)LHwNqJS$v?+Z~x|poO`W5$;w5 zu_&U952%`~NB~0eT`#+#EOAwhv)|&(csNeX+Dp;6vJd=3; zLM8RSfif+c(+6^r)v%hw);(&f8{>>Xt3Je=die|EMYh9DixM2yH#Xd{Cxu;wKc0bi{m?Zyp(l)JT1w?&e9uP+aQoE#43HmKSdr-rSe zfvPrtwD0Un>?JrOsdqn>CKt1Tc<%qmv>9A&nj-e)D#Bv(ernB+$Vf<5utG!jFd4Tc zFM}fz;4VPM>`{#_-2>F*`HkW`6Au>+auG&In(x{?qlm*hli9VYBtffQppl38Ri4xE zM6-?}Zxf=m?VGa`tvU;43S>XL5Lb;47O89#w+o{0Yg~6avZF?WdFJlv^bJTBw zu5HB)wfk_zO}-R_daOQ2CRX8sLWdb2RBrGxBbWKH#a-{f4B2^Rsj}Ryei@wM;W8i( z7lLhaLo{>at$aS0y+wepKV z$ZVrBtLE+kH3P1r8mCp=h7YhJjE-sIq^lkHpqOfFQ?hF*wHS||A3+Xm$&gwM$0QID zle@~yQYLt{$shf~f`{!AdTFfgN-0|)py)pyH`wtc)=aV!<$FXl+$>J^P-ym!Rxuws z4yY!ppRCzLDh@^WR#b~5sXM`rf;j%nCv1^-Vf$K-EkoZu+bZ^%D|$h3Hn|&$uOx= z^~)PfXId|Jt+FSUU%+9+-m(}t ztif1Tzs-<0Y;lyM^<76VGR=eXIL*fi(N@p)n+jJt5np!&4~yS&5J zz}=+>M3gsb;+{V_?BIsjE}9yZqZ2=z8!iuY7aaYeXJtLT!;q8>o01rqfDgv&pfZg` z%UXRnVnHqaE&F3m??-!Su`t5ZxN)Xcp0y}0-b0Q$DnWSQRR%319vj_Sm5Yc_+9#mk z{0~ou>UHt2+%9B5C=YvF<>)L0{C9|9vhxUdRlNz^>z1fu8> z6_DAGTb^=0m6*V7R>hxGN07mntRyRg2J6o^Be8=SPDY7B>v(KwP0<;YM_}DcMy69k zM3$f2k02*ERW(G*M&m=8CX}xCyd_G*;kiQ11rcQ97S2&tb$EX1LBnLNq6HT@lm6f> z5Ht};&Y8+h{;4}N#BT4Nn8=p|u&Xar<1!MAN&V%?%RX7}DjjdJ=JQ!>slth1oPZPJ z1LbsS6SglIc@%;jX2@Bc2wr^^B@83k2=8F(wm(wXcyHBe1=Gw7-{R9J{$e;?h)qJYFp=hr#1<{Ec9R0YzcHWE5LrRuY@O>$2P?3C9sREbffMO-v(i}=b@A|60K zsfRpaP-iee8eAe0jeg2sPvD2I{-`Y)0#{*Y$!~LArc&NiW+M69VPi5{dl7)FD3O8T z-KCJ-d6x#tO%QeZS1Tfdl#`F3dAv_BzBX?82?rt$dIBInoSjN>T|{n8^Jql_(WuHQ zsIf1y0xi~k+m*>`i7xDa}E$<|V;{T-)30AFHoq44(W$AZW4lH6?pwC?>5U{66HI~q4sW8^Eh zAs}_cvuh0}Ho|)RALW%)cN}SylN-ZJ-+RN>-#erupQ@5;S480UdB4q5((~vB8S)FB zu-t`c?3v0DGZZ|@B#_xck%tMNl})5abQx04rk-D#`CyLk(Vu4heDJO|$;XHoEWWr+ zof&erDO+rYn|cps{9MhCBp4-?FrUQCyfjzufM_^~dz$+e7K1BEPI7X`g{>K??RHUt zb)_+DOOogM49h*m-FiX{6`Swv^0Rlv(p;YHw5HMO6TbG>VZ}M@vA52r{e+fX=}X?A zGa)x%mQ|IN-o!>9lbnqfcWPLf zS_*5Jy3M&)fb~Ke0uo`aNa}9XdPj+hjWJ@*cy=4lsR-uD7S~O0cHk~ctw<~vsPgxX z0Wh>Q`K0w&rRvQ2t##p%mzY(%nPNN=Vvv?%AsVjUmR+M$iOX22&Kc^gY$vl5E1;gg zfSW-VApx9oFPrrx70$|B^WF9I`g*w|sUQ$j&kHVf6m5fdq#PQZx5S8IM6*VpUi7z) z0k5#i81ilaOls|;%}NBlsWf%590kG3WN&*Zqi!wz<)8_SH_+)6q2zCq&q%JqDl02P zTfrXOf*PVOZcMNZ6R19)cP>Q4NMx@UM?1EsP^IJ28#b@`x5x1iJ|uzk)3k}?&`pVg z%C=ed zHz%mmorm(^#p7Y!H`X{AA>?GDFK*tD8*U_b`FkbHw_u%We7;av~!fIAh-Iyi}#zj zgwz^%@5kWD&z4vS*DeVi?&+1p*pu&URd-=b`)s8)9MP1GW_G)HY+xx1PdV<{(p z%m6dO)1;P|OU#f+GzZ>j|3f1!q_vrS6I2d6hwFri@j256o+GqyZ(jQSSidCF>pNL5vU%Xs4GNyaksE^mCnUpv|Dwm_zg%cSA8GH0#{YKvj=D&o7(>V3X8C zW%>igp=`Tt`JxwuSBwgzmXpd4x}CPFWp+mXg&Q)})IIkt#9uvLqd*UQv|XcCgwTcc~l>(q`aB0G48Qc&sw5RU3K} z$z(+rgsR%32^S~pUF=JSK|-^W9>8UgcNA4#B;y6j%thFST~PCo0&CGP>K+s1V6HjS zdRH{evhOccsV}#Yix}Ab?&6=l&=%7r{I`N_t0g1V=4omr!DuMe6KJ%isV}7Vwn8Pf zp3H_^9LIAGRJ7=cS!#Jsct0qQs)JZ}t78!i&2Zu@C?p~d0#s!Q$tOLy$fd)2)G%Q) zpeLb+uI6#-(m~MC!yjUOouOcwyMjIO%ZtNop>EUbb@~COb#08lTz%tLE8t zw`MO8#nO78$BXUJ)9=B3QG`OCbfsv*5fZC(72m_zl2Fe-h~JDm;UI~4nxyyBlxmz3G4KT5ifCm@ z@Ee7+1y6g^YkrcoAbASLQ425B*vIGq1f|&XV?_yl^kFaG_a@Ae{g1pEv^ z9p7F4w!8!nk$^yguq$Fd{|2;M2mtsNTFx%T1`}}tq`qI)T!`(DmmSjJy4w4yb2$y`dk5GM#yS&KYODaZi}Z9GW`84$R~I zDh`lzki<45zu?|v=L=O^~@coImNmfSqjoSa%(pI1`e(Ug!^*$6>g#*&75ZJ;m) z=5%;=5;ZT6!%b(1@TiJ;MF(KYhueQ}YSR%DrubAY6s;_F60N0L@E(9cJB-S@#__f^ zSi}z_K*JT)%fL6pJ3;Cb%WvoLP2hYdd0HYJUW_!BOoU;Pp5}-MKhX`v*(-9zj61a5 zTSdSGO_x3aQ^U7ZK$>X^7ja3H;@L33Ur&n>flQcaC0!gjuNz%=;1jADFQ@c@6JvkJ zQDQN**dpYoiQx+mr#qX4$%lPPu$x+7#kko^DUs0H78Sz4wa6{oRE+QT)b>2rLUT!R zp>xdGQ)85k2_ynPI&@;Dy(+_An8zqPOsA)BBD#O3f*o^mDeJFhIax%_Vp}hs z9=LZIBNo9m=btRbK}CDX7Gk*d+Sq~Dk+2|9EoTs$p+i^+FRiwxRZ7yEFb|34Fj}Dz zL>$yYuRhWB2CNTV788$_zuK_x&l`Sj!UK2=kDstv348^K&)pj+8*e^9P0jZ}?(wzf zmUP<}Q!`W#TMk18KQ<>o>qFVj4b-es*lt!K-w=u^8vOC1mSSzlcboUqtE4uNM;+|S z+6#@(M5mR3Ohyd;Hw5Rg&Y)MVgEIPtq6TlqL{u^HTYr?^0HZJ5b zvuL{n-z}>`xAf_Ba|qo;C5xcwHHM|Wf{*xOMN`wXxwebHP^#Y_b^wAy#paZSxDWzU zvc9O}bx-E~7D@}yK@qo8!=LO-m5t{3KdQ@)izhY5ME)OWN!CnC^<$Ev8Z67f+oz7> zQ8Gw*bdxYmR!{fR#`PTezF5yn$$Sw=7$wJ=BSewSGT)s=r6Rma{3)X@Vo!^cs{S32 zq;QuZgmU^db`)K}PtiNK(}gid^{KP1sQ zI5t|VNgz~eooeA#_HZN)DoO{omMWgM`$AEFp90oXux36zq=&F4cJGv?_NhK zRgL#UWzrYGKa4UZJ<}^39WI`4#oGX1B5*wFSEqj3q z7p3h4vUSybkxX{Vlg_MiUNA0t(_wwykJT>kUI@w#|}lHtZ;9rx?OQgKiF zW08dntpCqF(m(I9Wv+CJV03aq;9Ph#B5-`LDwW$l>!07*wdEI+MGHtnvzo7k&Dv0j zLG!F$X$E)y*Z0MpdX0(Se`RV37^8W4Uvu!AFHPVVhqMj@V{4yhzHV*IJSEzATVwyZ zWu|}p_`Qv$+kiQ-FSBHcFHSTQHvI6T|LM+ifTIHwV*3(1^MU!ZFEeVF9{dJ$VOK?dg9zRbQoJG7;%^3XbsB@PGvxAFf({na-AXI$TLr||H}*RPKb zf}HLEg9L!fh5{|Qb-8yExmRF|$Ll_P&{^hFDo~AcFN=l1SZ<77ud7r%M15`MjK)cU z0RY4Bt~HM?Gd3)_ax3c@+af6`JdEf3%4nI^oQM;2>4~3QQo~QydBAe&eY<0LLqkQc zOMMkA+lCmU{WmDmH^@&xg&QLZyZ_^=(y<|~n{Z!*tZYO3;J3m%=N` z^L}Ch$4>UHjm6+5{3~WMAKhIbor$}t7lGXO#cDKydZN%HC9qp(J|3RJZ(bYx;L6=~ zomm<78WBriS{A&y0SB&JO791e?^$^5v0X6M)nNb(3#87|6aP@YZcp)valGrV$tF*q z&zT|$f<-8}8K=YA*}x+n*K(IL2`>gj`Io>r zf1aMDzR;jk&BLi&lzJG=*d`rTXIejMu)K9U-)s57@-WTd!@>PrAZ~+k`U4$#Z~(hM zVAJoG=%PpzO%%}R8kJ-`&lN>kiJr-DUH+=JUK-OljTU5x>_o!OVf1uEGa`65F>A+S(-&)=ec`kkeVVzF>Naezh?$q9_*qEx+>9<&U zxZWv#*CP2$xv^Q=tC|37YG!}++ch`WHgCA(Abf+!oEIK28HP_U?7p4&7!2cS(>JAw z@NVeMcS5lPRZrb6Z6E4X{bHudkB2rsg7+A9Y>sg-cym$J(5e_U7OjjP^bfx{9EM0} z9dU@2^~|57d2)1^DM{ghJUuH*(l4D`OBT3WbYyW7TWs0fhWG|Xdzek)p<{WBtMn6E z(#ynS*4CkqhWHC%#Ny?xG8^>8URiw$M%A{xd;%Yb#aR{yDGt(1w|!8sG7Jn-t!g@T zwqPVnS7a2C#ssHN%X@QgOa(Xvt^(|GtP7eC*rn|xevmPv3hTW@ z6Sh7UciEx)Qd^dnOSsv0Z%=VHwB*2iwl~8D5vC3C(@J37zT9y&uGGS;@@lEcn^P-B|v+A%+`HO(9T$gu}CcNb@^7{?DE!|mNv zW^&Kt&lLMSa&_NnV4OGLRn3~e*8DtOxL2FM1(@>N-3kA3p`q3niF_SlT7yR|0~o{A zV@C7m#?Sk7K4{@(U^kptbePltA;`0Nn+E%-LRRa|ly_`RuJiZsyfuXe(^|nZ<-aJ$-0wa!h<)K)cLlc1 zc9heW`;Bl=D{v#y2@ni?QWJVw`gGYieygNV&-(5pKI4@M7um|-~k45;wz3475N9D(W2SZyHts+45nt1baU_6VhBQZwuE{w;?s7Lhrw(7c` zO7+EoWLA$aFn0LGqT3}ONIKtGsGcU!Urc_o)1R_f45bX_@M2RY{FZV+(UGgQ9*c>T zbzto#Ek>uFoqk0_V9kv*(;M~YOS4;@&c^kO1(Q&+0vxsx921?lx@_mK#M}1+<4>Z^ z1}VFvF?&gOX5oI1`P7=`oYwzo&*y0R1;X;or=~aS;Xa;kwb+gOm{#OL(OK2dy>bSp z=T?(MDtxC~iDiMD%7sPtWk<_BixQwJ+GL}A{Rb!2jz%I?C_B1%(xw^ZZLDC;ss%rO zJ{5S$;TvCsYFpJb*Mm_+z6&{rwu)EL(ALzZNA?j*Gz&&ujORkdD1Ul;hnEP#*CGegx;2%~D z)Ek2%_w0AMY0fYtc!3XjT49#e?7pF8RbqoNdT(QX%_@%QQ`z>rih2Cnq1!m~aYn1l z{N!L!nvKX_MIjs1gAN%IZmgj zE2!|&cDxN*`&m_FfafJ_Oj1tLldiC>5LhX5A4NCM<1mrm@-Ev;(f`)>6=b!cQW;Ml z6MHlPt(7&@stkC&)wz1c`Pi=!!W#c=)J3Fd_g;>e=X&-g;{2{ZoOkRT&h*N)!?VAn zv(Lqp|LowA8Sh6Y2kq_eevLFqXSE>~62n{FivULT{)o_re?SV;kk%Z+5t=Ef;a%*oZ0UzeN3f7_$l^iT92A z^~(XYr**FX{T4edc(D_BAyzFgs#%6kPWuvS@iU9Q(ZUKnal?#_=Mr_T)BbNrnXRJ2UfF4whFTP0czPSAQb_E?+11K<*D1cSakG^6z-c~gSjVz zJ##()1b%#L+8QbO@41hx3McNwt1q_h`I;KOb6yUKwt9IS?caq)FWv3ao;&a_BBc+G z7I--v|6WGMD`l(+{`95q|L^|``8w#{$zal|AW||P{~Upq4@ui)TY$wel;rk*A;!9K9nVmZ|aB<~n-tvN5{9n^_zQt)~X;0bkyyJ@^g z+>nEjTOa&}3+k%~zR}WM?Eq~)h^f0H{Gt#g#-AiMCrAi#Ie9cJKIKB25C(mD8_?R! zBH+RGU+%Kg;{73dE(7h-a^F2_nmiq#N-IJyg1B@GV>M&y!$oZCsHZcS4tB8#U1FBeF}tc(hN*r+6CczG4{Fj&Hz?fh%cDVi4(yvk0*9iws}W zVo})s8pgE*A|!h7dO{Jp_XRW6ol3^lziDoUtuLxQ~3XqM&r*s$;Epmz$v1&#`+?#u!lgTGN*Lf2ja6~7;qvln`zMbqD7YBjYh(GP5i^J&Z@KK(XYdFLT;$M!CR<%6zGB!qEMix-gJcOWV7GcKm;?25Wnd@;q1r&tW zQO3LM9)5099@fTbUaQ;&c51}TE%kk@mQyOR{7i4OYek?o@eJf(AvM{Fw7X=hgjee$ zdI65(!Ip0MzVb6+#gRzLIV6rRL?y-42K z2R0PlZ)}P=W6c6Nv|hjSedpwYGOW;5xAbZV)T~>{@!Zymx@GSfU{q6HQF~h~I9HQ5 zqwH!c8rOW96WGLa9btt{waaY=74^;>1&vTKmtF3f#6fn8;&YoQ2G(|z^|9FLlw^vB zd)`^u8*px!^#P4(9pJ^~-ztxwdvTrMmYWf<8~yK6dL2TFa;+ZH=qc(QFj*coj0b|~ zyy}@{i++1Pi{p{J`KD@;K=@rUl2KFOx&7>-%U0?rdDyd!~iP_pODm=*g+2rHY78j?T+Lpxc08swaBGxcAO#O499g*F%)z{7F5A zMRl*B#>;Tq8f6!cWz)xkNQ=~$v9Dc;&|OHj0DA#k%3D|DUTquMd$GW!OR;(}MHgc4 zSrwW$$f)9&(Ke&r>EW~itoOZ0h>iHj8rOUh7nhU5H2m%%Gip_SLoucX8C;{Q|FVi# zKCGDN`r!f61fv=QxZe3dW%u7vv9PFZ^}`s#Vd&0;^e?iS&5uRh`FM$|W|(-_)Xe8PF*fv1{`zw{dRa#$ z7SLg87N=RbP{CsRoPcnK`X}o4VAmaLxM)sm@@6cgoMwaQ)P$|g^HhW3<&)Hvxf0i% zt79lx@%bf(gKq+m1uXh|?b~Iz0>B%CsSBfCwSuc5*fRI}n^aLE@mg?KpnjmMu(~W2 zHYF(5sdS0RSQHfl0?ICH_Mgo4m#W%%T;=Ds1fBv@^zQ?6nrR{Q=V|07&`&c?pUNKH ztM~IqEIu&wJkRiL@EGO4KA8Z5&ki+7)n1Z@un3Lryl~XU?z(9Qn=Pq>a zfHzwpit`CaX9kaB9qRTrXoD?q(o{PKalHk0wjRcy?h*7xzS|i+loJ7Z*@#c#WDyG) zWkM9i=fciX;)$eeTFga(AuSmH51}Pl0nA#Jf?vf{0|T0%jQU>3Kwz*l_l#;@7Z6Pn zJnu#kDxA(zNxxHrOBJkljD+za7!h)|7~Stuo>1SsPjIj&nbtonIlz;s7ojtF%}4Y2 zlj8LIu(MTD&v5OD9b4%qF?MGr9BMGdKR8VB*B zEoL!7W>b_(2WlwE+CZCoEk7e8k{mSQ1-T zpDph<s?<<5rx-e)^6I5zSXw&V2 zsALL;V`4|sEx9w;NYJ`VXYtZ%H0pk@OR;KfPg${>pJ(BXGxtZlD!S^0~fF=s@l2r=&eCTnxnt@lk(>u*q-R}LXw-q+1%}~;q;UbWl2t1(yOo} zzlb~kyzPY$FMukQ1cD6d3vf0<5XX(X1nQ*9AM~jFzU?^dH;{$g`(#GAr7-v`>H*<^Dlky(4)vP8D21za;SPToy zy6^&+woBlOmlZ6lZK(XStu^wQL?lOv@C!mqb3IfhwL{Yo@3*7)pUc`-)uHcG1jB$b^X+;dG=y0fzG zyeqbBq5^0^_-#{c7)Gr`|K6kiRN#G4e7mKPDb!Mor%8a9O;)$iC^@0gLwOp~2)P%{X4iFnLU>+e zxLbTwvMzLip}_8pvn|Rj6qpxSbt5#xwV^7Hw|M_wEO^kpNZ8%4dc0LrC2xq{?W335 ze^>6u7rAjuu`Gjv$CYny9PCvr2ZgqIg{KmHZM^hn*SGNXK(Y-`@JP zlAz>F=9PkA_VI>QZ&RFu8LN8Jeid;8eE4bbjy&tpyvZURJSDRwC+^%-l<%E^^?&a$ zNXdV67_?9Oxp5cg9Cc!IW}Lc2uQGs8 z@HYB$f=fbve%Z0^j~R)tmindTanEL15_FYMg9Lb#KqyijtY2)G^W1o&|IiUPf#Np+ z)TE?cN?lQYawXp}N?abkN*9VX532Ev2ImGqE2M4UUD%K#e+)4I9TtV_9~~C_ma;?s zvJZt#;J$Ki+7i}Pu&Ig44|rc!(XliTXZ}9+cdFJ!!47{9`%6>!#ORZ}9_~qtER_lH z!tOr!nNN8_&^W$i-`61wAg0uU>p$~aGm8RVT{CX+?K0iiw4&5j$z5aVsozJsW0|^w1GeBy8xeAjT)<6GAFVyc(%xP!t}@Stz=1TT?MXAg7Ra&CJG) zG(ZM{?&V*dqrdce#t$(AkY;UPYpSwdPnz?Q$Ee9fl{r<6NBl&%3Vq4jXk_A4l=4YN zuB9dCn(xfgrd>Df2=8i&*|0p`kh%crfiMWm0V!NU-f)*8LhbH(*M{4yIrH zSm%Z7T*y6!rqHY66U*k)k?UW_V}OX=k&imP^lKrMc*c*sW!{g&Wc-VS`F~8-jI7D< z;=k7a%6ysR^jjbd`R~d$BQO5F{g(8yBrEKz7XQJO{++3PCIxs^#mvZpzf0`TgaI(I zQ3IsUEwK0N9{fvZ?`LLJpwG5--^)kJPeCq4v#hc2|$37bUy+`;tTO0=l4$RM4PE7fj`)J|g6vR3|^FHtCjGcs0emSNS zelJ47^2hTSobF^XXt~u#Z5;sFqdO%pI#UB{O5Ijt)aNms zhA|nt`Em~^tPXgkO%g_gP$o_V+GXk*mj15 znf|#&dOQRt1@h=Q%Wpy`&~Z9745Irs9K0ddEx%gT)VCK6Ep%_yThXF)EnVFU*RdL$ zocDjV_nl!)Wl_627RE-#K>?*?MjTNQK{`apAS%PCprRlkAOfL>PN<2>NK^zI8=Z)# zs7NzFfB+F9MInIn00BdXgit~fLfSouqvAOC*L}X{-skEcewee*-e<49_A2kYRtXr$ za=Y0BRLIJwK6m6qz(sYHe2s+;Vx_5Hi+&d>)q6X!C)}fKYQ>JlU^%cw#v^%0lHPpw zVV2&*6L2qd+Xu!d&>AdiCO4(D(yXv9;kE?A_N{BZ8A#hM!{3cOI;Ic+1mq@V0f?lO zn&aDAJoS>hhVOGH6qu%PzN)=GErur;uba9O0e8AT22$6)RNSkyb}>aORqY+u-~iYI z>7Vo`5Y2wt2;Oeqf#_&lD163()r@b;?3zqK!5TM9u5A+Pk#@n-sd<`{W|RdboNy-x zU31uUa>`1{rbIbc56-H74s8S~4P?ckl?Q+4Ifkvx=vjlMyBEIP|L)vzd|||cZ)TtO z&Y@S)>Y^I&J2wt(qUZHL)Ewu8Wgo#M1KCH$`v*ON$lQBitHw@k`F8*#UAN+IR>(2@ zLfwAn`(WL$F&x!cp;q8q%TFjhag(kz=;>A~ZDTX4!C3pROvIwCbutxNka1~H_*Q(3 zR7!b<2z6vWl3S62nA~%@@tradfhy~qA(Uz=GBP7Pds8`RVV4gqA`QBjvCHNEwpRw_ zD!DBP?~lJ3(ynZtLULsFxF_au(vo3V%Z=YOI2Q*O z-zqMccksSIK7YMzjAYqSIV{n(ue8Hxe;E+>LlwINR}PzhJLrrb%-iQ#)wKzu6Ta_#vlk>s~1)?*;cJNMN8 zYDJ<(a&|Wg(#y}0(|#Q@=BqJVnj1}8ec8Dl=X;CKp-M}TZ&%t14X`4$GbS*!yH@utBCsi}$cJB!^Oc;9)Na38FPCH^dQ?hd+`poC|O`|<3 zc<6p)@Z9PTXO{SDt7l#y9YIYS<3!s?gb+DGpZ=DTvda(%kxRU&NeE^OXMy@W&c0^# zbOFMy82`)lrr%E4KUz$i{^L@eXHxKhCqgSa^5}!s(K(l-49h~IG-o+^4I=`#&TaY# zn_s=>^&|#6k2bcG1p2X2$mcEBA?v-olME0WhJ-#&nX+;aytXyVl6Zsxl47%yI=~Cb zxyhRv8%q14OdE=xL{=6MUfPUhPdZWBvy{GiJtQ*!8i8kky7D30Nj>SNrDp2YjnGcl zU^kI=Ou1liwZW;>db(|NZ)#Hv+6GorWku`)n_4?+H@MhYlUbo9&=j6>PVb{LK_s^k zKJ}t=CJ`rW&Ne96pEu?GWJsvFp%eo%lYA`p+~bLz*SkeL??;!Dx_uOsdW1it=(73Q z2LO-wJs`y%kY{kR7wjzIL28(ad^Qs($E&Z4Q({Z8<&Cov)qQLcm%1<^=gtwi-KOFY zVbSQZFvW&MN|B)bMOj@O0K=HHabeUXvQ14=pUDN#7uK!WDIq6&y1e`0BF1=0;=<;M zP=b#TG*vV|lZkmoPz797@w+hYWGy-qmsPAusas; zX*KBEQSq&~n>;2EnBMpZ|B`IBUr)L??0IhQq%u$&NAos?(cGtl#X!l*Db)3--A11! zh&_#-veOaOa*!hEsSijd+1;e0eS;_H>rlz*5E>}kC14OOG#%?RHtqEt?0vs!J<7E_ zWhNxm5UvPekk5KppO3m2KgE+t_2yI6`g8K{*k~V7DcXhq{Yb>$6B7TZ^(9Bw`ar)| zG7*TrDzT)1$T9J9izl^q)o35M8x1h|(JQj4G91fQ^;M+fW43AP!2o_I)~1i6i_+PX z6P`BWPcGok4!WW40mMt}LW5T^^Db0Y+pv7$i?0Zzmtd11(Y#8HY|&GK%Y*kVK2J50 z^`Ik17jCq!^GCUYK%-Gj^QDGJpWrcZ2?SFqUNL)Me)er{M`2O!Xs#a{odiG_7-bj& zzhoMn%Fl*Gn$nC=bu-StE|bzh0Tlo$`Okbjc{ zXij-(;gQdK1f24lQ2r>CU&i|Mwiknn3MmMHHqUrr=r|8kLM@~x6PiQu*UM$An;ge1 zq(>u1y!y(cWwPz ze_9zE^+{cPLi3U3`RqPz~6YZkvcHcVJ_g_0THo$`GA$z@Mve9M8BotMi~=H7i$ zvmfVhS)(U%YBtP1NB0VJ&OCIA4$8cDp6tJ6<;3O1mzaj*7lciv%An^rt__NsuDsYb#Yr)w&&N;hiBH5FRC`nW zuwrQf*F7!!MRSArso3(V+Et4ovZef=C+J4flEyyq-jd5( z16EdDIZlOvw-169SK3{)jbIkH^CxR)GPdYx6Mmz1Kl@J88-iO(f)KH1Qi}Y zPXVO7FGm+Pp)8ur;(Do20h@R#$1G8~3@Xdr{5y@wk9j1O2&pKWjeqLNPd{)Q(93YM z^8t~!Y_%75W8}hf=R)>iPJgft!V0pCM}9&j_?5&1hxkLb)f1#u8xN=|hS~@bY|oJP z*9H_gBjSD6K`3iLl zfJY4&>>mdNd$Ns}?IB zai#FqwRhAD^!6O2U|7XQ(qv}-*UvdmvV1tM#EN7(na-_u-xfzugX6U;p+`VaUR4Xk>ic4svuK_2rcv~-c zeT86JUrXJt#5i?YsxEtSV`r=4qctB6-B8yW&go^C;##=n0b!n$!OAtL;4=V%xORVW z{10Eo7<7a1elYj}Yp6y*Xhv24uMpb7F{i=r`orPj_7rBiH97cATX~wPP9_FpWzxR1RRoaH* z1mm)OFGc zalxCUkvd}jjGd{(BygKdD<_Itu|4CQ8MN49`|?^gjIfEQ~N7IpV_U zX+ZhJ+WM%h0pdJbtlmvdNJz^jrzPbI412cIEWv%Mcqp7w0A)4pSJw_08{=4V7yVTo zU+ipdgAw8Sy}~Qyh0yb4T}vEXlqR_p=5mzkJ+=rgI#JGF+lCUEJ8Ujo_uv2!!t$pF zwJS5)8R2~o1|uHGNd-7Y%0mUhhlP_MH1`odD_p|J<3}+7sLT)25q&ki3MKe@9-|h@ ze{pI>%%ZY$#AUV25YxcWhBBq|tTJkt2D12^_rqdZ%LNa9GaNlbBm%fY-Jr_yEkHD` z@XQn%YFT3jn2>`QB)9PX4`*&HULuR@)W*D<7BE4SJ%6<}80dzEN8At`2pR>)T3h96 z2LokUl$}yg!5csCssn=6SAJXw2^=@7d_WWel1z?{s||XR zlnJA;S?R;q<$ev2(nDZ?N^GTLWS~677ZTzCMxCQP?c;?LS`DhFDMi{&{FBCjx*hGB zd>adD#EnAfF_R`&B@pN!Z4uQkqVukBAA!*4t}ja0Urh;n3x-K}9Mt{#W35Nc1KnkW zb1pGIxZVU}*o#w4#uTj0U>Whe5msfT^iuzgld0zPCPSg?0UwUp_}o#3)?Rq|)re~S z2AreToka9Ky5dv|IC^qELebN@Tb2QU23G6& zgeQK2f-b|wnr5u(%zSj-R17eYzD5P>B#Jk9-|^Iy146W)zt>0h<6S*fXi(B{z(?fh zvQ(S|ZJI7%MG;Mat&)vRvxBM0O4ieTruscqrx9BWwl4)wfoU$o$j+n(j&U|jX^(O6 zW-97*4p@I8(8xxk$M<+xs=~7)OX`yg$NWscs$nFWfivVg42C^@n$1zYNAz;#w3Sw zIu&jZ+qI1)iOd?oobr7T`p$O$jcg|cf@=|765o7X`szn;>?a}lm@WZjN6w(@&!XB% zfMt}BP0y!34tb_t5P2!G7rfn4$2YDQOSEvgD47zQ0!}$Tc}0T5?{;DL5;F2bf?)c6 zT`_z1(?={K(@|v%ckp8+2sfgY!v*xF}wGVhtS@#i6*S4^NBRgKyuf9)t5$>VecGpJ%z+u ziMFWsIE{iH*#Kj}*s%^;19_s;_+mrA6g=p)*Q-qjVU)fq;Y5%_iT&~a6rt(P%ohvh zQy@jgkJDA%1vBikd$_;!)qs77%RUcaLII|m+__c7+q-A}7(Hm|kJvlV ztY^qGjf{m9c!G2wmgHHPybj-k`J;-BlNUysRSLxZ^Nn3MH|U`fk`H;bYFTRG)x{@^ z2kt}ZlKXv{{e;=HYb z6z)8X=u6l9tDe-1;UJX4%5Nm2M@)cl0p+#Vv(||K<7iK^T#0=cjR}-?cb7&9sc#vF zs30+l-AJ4Uo(WRNvXO@GK7i@}rP9PYD%19j<@_Q)Tmc`>wYLl8JHIxkO|7>qo{PKr zMy8Y&KY3;F_6%@5&Yq9<)Q0*fZtXtx2TCy8Yfm@`up-%~hv5}lahG!<21Af6EdF*P z&0KsDgt+`DzPS8K?Za`Sly~o%X_61_e>u#~P&NZD17rmTG5lut3B<3RKmM z&XFmx*|=8dG6GKgjiPKe%S$ir*JEtu<#olDQ~w;%9>C$oxW*k$Fj~*Uefa3`ant;4 z3zs&zt=hJGI{H>EaRa`1^P^Az^@kC$y70&|E4McYH;blwpyfOPgzpfut;RO+C1go> z%bqSATSU>d96xjucw`(CV>J&{I~N51ppmSvAf)05dj8j-FVyFW)&QV#^hmpX=u$M5 zZ80?p6}(Gc!*?(uD-KK%Y?6)m4yl#kj-P@1NaeY(Wf66vaVHNG>H&=4xgTI3^b76# z%bnT)Zr&0*AYw4Pj(TZZu5k!W0>;wBj|piga0M&5w4Me4*SY-Iz*Mh1X_qjb>Zs=6(2u zRK5T^2x1*t&hPm3PyMjhn^9e79x4b~=xqB^f$$g9IX&E;zLv$Lh{IR*t&KfG2|T&( ztM{H`A?GI%)qsmR_nKI{v6M0@Fw9u(y5DU_A3(`7uBYzKtL95q3WNoC4y8XfGg5L% z+kPT0?mU^>4>p*YJ|Lq_1uL)pb%SbMMAbo8MR$eYi8^V}6e)ti2`Y#uSjDGk3jf)H zPplT5+t8F8QnmTUj-w9kt5^tg?dp6oBC7#p%KBrOOLD%5|@K9XP3*ir20LJ{H2tdt~4T8d@LhaB%Mv?wM5WdtYZsfk1FW_$eB`YVBHiPXy^YS+PEICG-Wi$FO)AX=A+sBs zy&nt`&LnuA+ZbJ#)nT~dtBJPE6Fo<^BN-s$qU&a<6q!UjsO**;ALr7th#}Cgqa$u3q$JtF_#&rpMhvPI8o9+dQ^0siB3IB1@2JUAV|%ooQD1! zOv#CcNwBvj!YB#|;y7}mL)>++C`(7FOs$OGkp)Nm#LIi+n1uLDcy@budE;x(fTY5P zV~&IRF731jYrY~CneeIZ&M`ee{xWE;PMAg})Fln67`zRVoF4 zqL}*N8QA2lz(ETk*9U+7qT82fM7-#q)&4e70y6JJy3ho7*0aq5tlgwEn!LSa<(Qq2ap!*jtG5-FwXwT$JqK=PN4&5WQ~r{B z^iwjdU&KH_vNWDTk>$zzHsbiZ{X`;EWY zrWl#?%J+lD`SULeBaYEPFsmjw9ERG5mcR*Bu}T3ijJ6f%nbTxbjOECi_R-@S!?j+f z`Sn@p@y;c#*rYJb+M~cz%m*NeVl+C&slcKKe2P&7kTQIc?&Gh$clhfL3U2^qmf25d zUh1rBFYf^N+*v2qxUO_(GCut%=XLYswVq26gmF&*YNV)FA&3tnFI&+)U`2C-(TSC2 zhqt4;-h#xtJ5rMm(3*E~3*R<2pAhH*g}i;*!3exRWbKd^HR?|9E`VTooZo62rb?E_ z*qyka*k4z-2^?eHxXqf@^#<{pyUFp;2;653N5jP%cJ|^;? z2H>Pm(ntGE4%WX7Z2sX(ZN~%3o}*Pp#edmxd)_Eax+%o4C)cGdYg3E`i^9EXtF7iFsH%#y2^3gF!9%dwvHrgj8)3(v9|AO1 z(ltR|D)S1vDW!zDx)j4O>*~*6wNFlW<)!kM5Wk7TyX`iAn0gG4_^r!`# zHn-}=RcL_GF-p3OiCR(MnHRSA0J%v7ED`m?>bdh}P}?r-Mho_f%6w=>^R}uILF85- za(KLg8_{KZf1cQYQ3g6R{d62g@zjthUH3OoSehx%$hstxQ!v$sMNxqxaZ!yvl6g3m zwBLzh6+c-2?4#TIAuR5I(cp1Gi??#XBi)ML{7vB#ag#weW-#f5^_67pfUmEZ3 zpCgN=--Cft070L63*p*26Z$I85hR^!rZ2l+jqu(*-=KmI#GO^79XW-s>~b`iQoo#F z4%Q3bDk4_?*!M&{g5uA9>EmmDZbw3B#T;Go?20E z&)u;m?EF02p5goj^ynB--i=NCVBg4izn*uXg$jfbqo+*JZdy-p1R?!_K((Fm>HveRP< ze6G!prFh~s`*}wA$jlMs98z5=)FXe|tjpvrsp%@JE%YFwME^+sFf3;V6g`<>FNwr+ zJM@mB;OWd5TH+1K)QjV+q6W2k(wPadcgrt6dOWOShfp3>yvy+KAi!a~ptW1Rh<>*n zJ5cp*IQ*i_!Yj%O62*rZy;>Ol{dPo1xTs( z9LVsz-;Y$5*+e-yi$HK}>eo^nd^0rxp84;$E6kqN96i`I{9Iy zRrrTCj%L31(%Wk@Xyd=Wu=Ju5G?G7BQ~y||KNDd!JA_$t*^}$c zxh1Ew2RySfcbmrQ$w$9jXP0F4=tFEd@=K%6XhDNEe`k=s&dX5hcJ@Lm|KX3vxYXR3 z)NO$n=pk5t-^=izHxyrvA>?K#`j>Pn{K*O+tc#(P8r<3^f$qZva-!X`Ws0w!vt6~IaucaEYHylX>W>84PW71R3YZ+qE@8-Y#)U@ zL-x_ZX@|&|&dR(gks+3@S&!05ieoazu;enxTR&6+3GELQbDiiovwsw+i=|dJ&<}UW zH0dQz9xr=L3#b)pIYcvj^svdv355`1qIO*Ln*g2h`RoKaUl6u0T#us^$k!5VdfSrK z8UBrQVB~Iqbi*DJK6i{s!z&o~Q`nUh-QWmwRqY;st)gf${*Aa`ABo6k=Q~2SG4~Q` zJ+np?eT%e_V>x~qR=si;$2)wpI5w-Ghyr}%U*%`dp+#^|$9mS4N7dYoBz{?|)j{M5~jB z*bQ-RrS8w{2qN38SDB3HlZ0J0M4Id1lFAFa_HhDRh0h_V^W?-+|@-O~(|;5BcuHhuA?enH6at6sC0 zp-)*Ct?%LtVTd~$;k0c%wo}rQ6`yb|UT79f8ORmTPYwUgdX{=Gf_59L&@T0Cu}&F) zp#IVmx%G(LlKiBk@Km+W19jI(&xw(o@uDXsmdW$O^uN!0PtRf{cqK+Om&VH^{^Q+j z^2~zT7KBEZTpwkgc*NlcF{A{AM7~+A#4f1w6$UfnG;(1ZI1dJMb*wz)AZ*CM#$5H6 z%;5#C*X)vwsNL)7!#^(L0xaTdB6k812MyAV%BmP>0K49x@TJrp>0QcG%^gCYJgZ6@ zEzav+6$g;ew4aGCiEFU5U&y*UdGF2)7X!MtHg}+mgC>+NBizmc0>Ym(&bApxreO%e;C8h!5fKtDe%gI`mSS`Pyn$T?T1AEWK}GVawq5+UUk!+nS4M4ZZ6--a0FVas8y$ApN#K;v^(LRAt4tCS;1Glh0x* zNR$iy)wN;Cb!ov{xE*ovvx*XWcS=HL-;~qJa6zNv_2#=zf8%kVG7Xt0Wrnlz4OJZP z{Wha-BF=tOOHhN|w03X1la<5}0?u?%)>rVdkJXK250GF)>VX~dlcFNqu+%y8rz+oi z>F070U!D17DbBB4<gD)Y`|4}ySGx2Bfuz!;?^mP@DW$2Rc%lHUM z`w|25eK)?e(7y}S*Vp>@BmNJ9^b;g(vE<$VzQEgeP4%y$Ny}`-??UxmsQz6S>l5?$ z`z`*V-1GlLDa!Af>bs^|s)+u7u)gVcq53XV--YUnDDbihT;KhTZ_psWRrm3Kq_X4p z-T1y6pUHaPBP0J&HsO1??OzFx?@sJ@C-%D&yR1gX_gj2f?VRuVjqhoie<#ZSZ@4$Q z4&0^>AKuqOUGoW){Rt7gc%pFyjyo}FDNeP0?RsO=eZFH`*=S6~wwI6JdHIwE4W zJ)cux9mKCk&0Pe3YS5p*!qsxI14~5>`2Ngd*FInz$2^Z;BaT+C`B*T^W?^B7wLewJ zdZSp|*XpiIo>UQ$>$$LB28M=OZ6$d_Lo8s>qd-d__sn6SZJ$5gR$m9QtF0FD=X{H* za5}^GJ`d>p=Q!n_{m+PM;Db#d3n%-Ec?YaFLI(Rzr+~S|J~h0&{-QmjDB; z^ap=63c3Cq-Kl6lIn82v#MJ%>?Q`=S*ig8R#d?z^7fX&{UyP&w1~mD(6uO>r+s(L% zIbeH=-41kiZIY|v(zjv=X>{P1O=(QMk-?#@#)CN3NnX~dC(F1$&Gq2XV*f*v0ST;O zhl<1s;oP=}m)@p27=?{)yH>I^N*^~txqD?9S-JL&K5lzX(t!F+vk(&F%EYP3T_R^H21E8FqX0|FjW1s`j z;JM`6aVaVl5yI$7T6_wZH&Y$oRL^>RyN#S`W(Unps)#n1h5Xi5FP9Ju^y~|@!{qKM z9tC#(f!zjNTPT7qr}Hxg#T-@;+Q%VzBjmxR(mI19IRfq&)_c{%2;Li0%O5L__nAEv zaYX39LLW7`*o&PWtv5^4pA(Zb!0mj{#~brM_GM2yE(4&gi?P1J?Wk@54!&5WEaJMvx7&6eohFIsh76gswr7IrIu58!Y56 zQmmq*3}ZjH&2+VxLU`cjTJsUZzw;wDPXnYZv)H#yG8jHHpYwPfe__nm4 zw`(Sn9oM;{%{~EKDA)Bo3c8@d8_efqB=TR_>03sLo{sN~1M8M~a;sw|`Dq>H3fvVt z;#H=I_S+Pcfb}!QDrz&|o1H(a_}-E=AuyyEs!Pp)W6PzCM=h;6NWRkOZ}b@Gs|IPZ z#7UF`aztZh%aFo_;}5sgLftHzhMSWI0oy^m%VC#wQ94SjBqej(;W1iew7uLR78^v`@0#Z@vd5uJao zu*~i2Z;QAjGGVP-;&2@*>bQ zMz@SUpY@#9Au*l1@!(WFXCl^=zPMR&vUaAGab2D(Q7t)roP{Ttc?H|P*&gWgQn+m+ zrX)PX8|hS6xN$3_GTMPe3pt5=VOpS}((ZI=>20;_0WWVSH(1DjKj40> na~ODF_op`mxOqAI2RL~7{P#2Zc5J=H&uenT^l;9h3s?RZft)V8 literal 0 HcmV?d00001 diff --git a/server/docs/design/bidi-producer-consumers-streaming.md b/server/docs/design/bidi-producer-consumers-streaming.md new file mode 100644 index 00000000..da0aa675 --- /dev/null +++ b/server/docs/design/bidi-producer-consumers-streaming.md @@ -0,0 +1,339 @@ +# Bi-directional Producer/Consumer Streaming with gRPC + +## Purpose + +A primary use case of the `hedera-block-node` is to stream live BlockItems (see Terms section) from a producer +(e.g. Consensus Node) to N consumers (e.g. Mirror Node) with the lowest possible latency while correctly preserving the +order of the BlockItems. This document outlines several possible strategies to implement this use case and the design +of the recommended approach. All strategies rely on the Helidon 4.x.x server implementations of HTTP/2 and gRPC +services to ingest BlockItem data from a producer and then to stream the same BlockItems to downstream consumers. It +does this by defining bidirectional gRPC streaming services based on protobuf definitions. + +Helidon provides well-defined APIs and extension points to implement business logic for these services. The main entry +point for custom logic is an implementation of `GrpcService`. + +--- + +## Goals + +1) Consumers must be able to dynamically subscribe and unsubscribe from the live stream of BlockItems emitted by the + producer. +2) Correct, in-order streaming delivery of BlockItems from a producer to all registered consumers. +3) Minimize latency between the producer and consumers. +4) Minimize CPU resources consumed by the producer and consumers. + +--- + +### Terms + +**BlockItem** - The BlockItem is the primary data structure passed between the producer, the `hedera-block-node` +and consumers. A defined sequence of BlockItems represent a Block when stored on the `hedera-block-node`. + +**Bidirectional Streaming** - Bidirectional streaming is an [HTTP/2 feature](https://datatracker.ietf.org/doc/html/rfc9113#name-streams-and-multiplexing) allowing both a client and a server to emit +a continuous stream of frames without waiting for responses. In this way, gRPC services can be used to efficiently +transmit a continuous flow of BlockItem messages while the HTTP/2 connection is open. + +**Producer StreamObserver** - The Producer StreamObserver is a custom implementation of the [gRPC StreamObserver +interface](https://github.com/grpc/grpc-java/blob/0ff3f8e4ac4c265e91b4a0379a32cf25a0a2b2f7/stub/src/main/java/io/grpc/stub/StreamObserver.java#L45) used by Helidon. It is initialized by the BlockItemStreamService (see Entities section). Helidon invokes the Producer +StreamObserver at runtime when the producer sends a new BlockItem to the `StreamSink` gRPC service. + +**Consumer StreamObserver** - The Consumer StreamObserver is a custom implementation of the [gRPC StreamObserver +interface](https://github.com/grpc/grpc-java/blob/0ff3f8e4ac4c265e91b4a0379a32cf25a0a2b2f7/stub/src/main/java/io/grpc/stub/StreamObserver.java#L45) used by Helidon. It is initialized by the BlockItemStreamService (see Entities section). Helidon invokes the Consumer +StreamObserver at runtime when the downstream consumer of the `StreamSource` gRPC service sends HTTP/2 responses to +sent BlockItems. + +**subscribe** - Consumers calling the `StreamSource` gRPC service must be affiliated or subscribed with a producer to +receive a live stream of BlockItems from the `hedera-block-node`. + +**unsubscribe** - Consumers terminating their connection with the `StreamSource` gRPC service must be unaffiliated or +unsubscribed from a producer so that internal objects can be cleaned up and resources released. + +--- + +### Block Node gRPC Streaming Services API + +The following protobuf definition outlines the gRPC services and messages used to stream BlockItems between a producer, +consumers and the `hedera-block-node`. The `BlockStreamGrpc` service definition provides 2 bidirectional streaming +methods: `StreamSink` and `StreamSource`. Aside from the gRPC service and method names, all the types will be replaced +by those outlined in [hedera-protobufs](https://github.com/hashgraph/hedera-protobufs/pull/342/files). + +```protobuf + +/** + * The BlockStreamGrpc service definition provides 2 bidirectional streaming methods for + * exchanging BlockItems with the Block Node server. + * + * A producer (e.g. Consensus Node) can use the StreamSink method to stream BlockItems to the + * Block Node server. The Block Node server will respond with a BlockResponse message for + * each BlockItem received. + * + * A consumer (e.g. Mirror Node) can use the StreamSource method to request a stream of + * BlockItems from the server. The consumer is expected to respond with a BlockResponse message + * with the id of each BlockItem received. + */ +service BlockStreamGrpc { + /** + * StreamSink is a bidirectional streaming method that allows a producer to stream BlockItems + * to the Block Node server. The server will respond with a BlockResponse message for each + * BlockItem received. + */ + rpc StreamSink (stream BlockItem) returns (stream BlockItemResponse) {} + + /** + * StreamSource is a bidirectional streaming method that allows a consumer to request a + * stream of BlockItems from the server. The consumer is expected to respond with a BlockResponse + * message with the id of each BlockItem received. + */ + rpc StreamSource (stream BlockItemResponse) returns (stream BlockItem) {} +} + +/** + * A BlockItem is a simple message that contains an id and a value. + * This specification is a simple example meant to expedite development. + * It will be replaced with a PBJ implementation in the future. + */ +message BlockItem { + /** + * The id of the block. Each block id should be unique. + */ + int64 id = 1; + + /** + * The value of the block. The value can be any string. + */ + string value = 2; +} + +/** + * A BlockItemResponse is a simple message that contains an id. + * The BlockItemResponse is meant to confirm the receipt of a BlockItem. + * A future use case may expand on this type to communicate a failure + * condition where the BlockItem needs to be resent, etc. + */ +message BlockItemResponse { + /** + * The id of the BlockItem which was received. + */ + int64 id = 1; +} + +``` + +--- + + +## Approaches: + +All the following approaches require integrating with Helidon 4.x.x gRPC services to implement the bidirectional +streaming API methods defined above. The following objects are used in all approaches: + +`BlockItemStreamService` is a custom implementation of the Helidon gRPC `GrpcService`. It is responsible for binding +the Helidon routing mechanism to the gRPC streaming methods called by producers and consumers. + +`ProducerBlockItemObserver` is a custom implementation of the Helidon gRPC `StreamObserver` interface. +`BlockItemStreamService` instantiates a new `ProducerBlockItemObserver` instance when the `StreamSink` gRPC method is +called by a producer. Thereafter, Helidon invokes `ProducerBlockItemObserver` methods to receive the latest BlockItem +from the producer and return BlockItemResponses via a bidirectional stream. + +`ConsumerBlockItemObserver` is also a custom implementation of the Helidon gRPC `StreamObserver` interface. +`BlockItemStreamService` instantiates a new `ConsumerBlockItemObserver` instance when the `StreamSource` gRPC method +is called by each consumer. The `ConsumerBlockItemObserver` wraps an instance of `StreamObserver` provided by Helidon +when the connection is established. The `ConsumerBlockItemObserver` uses the `StreamObserver` to send the latest +BlockItem to the downstream consumer. Helidon invokes `ConsumerBlockItemObserver` methods to deliver BlockItemResponses +from the consumer in receipt of BlockItems. + + +### Approach 1: Directly passing BlockItems from `ProducerBlockItemObserver` to N `ConsumerBlockItemObserver`s. + +Directly passing BlockItems from the `ProducerBlockItemObserver` to N `ConsumerBlockItemObserver`s without storing +BlockItems in an intermediate data structure. This approach was the basis for one of the first implementations of gRPC +Live Streaming (see [BlockNode Issue 21](https://github.com/hashgraph/hedera-block-node/issues/21)). Unfortunately, this approach has the following problems: + +Drawbacks: +1) Each `ProducerBlockItemObserver` must iterate over the list of subscribed consumers to pass the BlockItem to each + `ConsumerBlockItemObserver` before saving the BlockItem to disk and issuing a BlockItemResponse back to the producer. + The linear scaling of consumers will aggregate latency resulting in the last consumer in the list to be penalized + with the sum of the latencies of all consumers before it. +2) Dynamically subscribing/unsubscribing `ConsumerBlockItemObserver`s while deterministically broadcasting BlockItems + to each consumer in the correct order complicates and slows down the process. It requires thread-safe data + structures and synchronization on all reads and writes to ensure new/removed subscribers do not disrupt the + iteration order of the `ConsumerBlockItemObserver`s. + +### Approach 2: Use a shared data structure between `ProducerBlockItemObserver` and `ConsumerBlockItemObserver`s. Consumers busy-wait for new BlockItems. + +Alternatively, if `ProducerBlockItemObserver`s store BlockItems in a shared data structure before immediately returning +a response to the producer, the BlockItem is then immediately available for all `ConsumerBlockItemObserver`s to read +asynchronously. Consumers can repeatedly poll the shared data structure for new BlockItems. This approach has the +following consequences: + +Advantages: +1) The `ProducerBlockItemObserver` can immediately return a BlockItemResponse to the producer without waiting for the + `ConsumerBlockItemObserver`s to process the BlockItem or waiting for the BlockItem to be written to disk. +2) No additional third-party libraries are required to implement this approach. + +Drawbacks: +1) Busy-waiting consumers will increase CPU demand while polling the shared data structure for new BlockItems. +2) It is difficult to anticipate and tune an optimal polling interval for consumers as the number of consumers scales + up or down. +3) While prototyping this approach, it appeared that `ConsumerBlockItemObserver`s using a busy-wait to watch for new + BlockItems impaired the ability of the Helidon Virtual Thread instance to process the inbound responses from the + downstream consumer in a timely way. The aggressive behavior of the busy-wait could complicate future use cases + requiring downstream consumer response processing. + + +### Approach 3: Use a shared data structure between `ProducerBlockItemObserver` and `ConsumerBlockItemObserver`s. Use downstream consumer BlockItemResponses to drive the process of sending new BlockItems. + +With this approach, the `ProducerBlockItemObserver` will store BlockItems in a shared data structure before immediately +returning a BlockItemResponse to the producer. However, rather than using a busy-wait to poll for new BlockItems, +`ConsumerBlockItemObserver`s will send new BlockItems only upon receipt of BlockItemResponses from previously sent +BlockItems. When Helidon invokes `onNext()` with a BlockItemResponse, the `ConsumerBlockItemObserver` (using an +internal counter) will calculate and send all newest BlockItems available from the shared data structure to the +downstream consumer. In this way, the downstream consumer responses will drive the process of sending new BlockItems. + +Advantages: +1) It will not consume CPU resources polling. +2) It will not hijack the thread from responding to the downstream consumer. Rather, it uses the interaction with the + consumer to trigger sending the newest BlockItems downstream. +3) The shared data structure will need to be concurrent but, after the initial write operation, all subsequent reads + should not require synchronization. +4) The shared data structure will decouple the `ProducerBlockItemObserver` from the `ConsumerBlockItemObserver`s + allowing them to operate independently and not accrue the same latency issues as Approach #1. +5) No additional third-party libraries are required to implement this approach. + +Drawbacks: +1) With this approach, BlockItems sent to the consumer are driven exclusively by the downstream consumer + BlockItemResponses. Given, the latency of a network request/response round-trip, this approach will likely be far + too slow to be considered effective even when sending a batch of all the latest BlockItems. + +### Approach 4: Shared data structure between producer and consumer services. Leveraging the LMAX Disruptor library to manage inter-process pub/sub message-passing between producer and consumers via RingBuffer. + +The LMAX Disruptor library is a high-performance inter-process pub/sub message passing library that could be used to +efficiently pass BlockItems between a `ProducerBlockItemObserver` and `ConsumerBlockItemObserver`s. The Disruptor +library is designed to minimize latency as well as CPU cycles to by not blocking while maintaining concurrency +guarantees. + +Advantages: +1) The Disruptor library is designed to minimize the latency of passing BlockItem messages between a + `ProducerBlockItemObserver` and `ConsumerBlockItemObserver`s. +2) The Disruptor library is designed to minimize the CPU resources used by the `ProducerBlockItemObserver` and + `ConsumerBlockItemObserver`s. +3) The Disruptor library does not require any additional transient dependencies. +4) Fixes to the Disruptor library are actively maintained and updated by the LMAX team. + +Drawbacks: +1) The Disruptor library is a third-party library requiring ramp-up time and integration effort to use it correctly and + effectively. +2) Leveraging the Disruptor library requires the communication between the `ProducerBlockItemObserver` and + `ConsumerBlockItemObserver`s to be affiliated by subscribing/unsubscribing the downstream consumers to receive the + latest BlockItems from the producer via the Disruptor RingBuffer. The process of managing these subscriptions to + the RingBuffer can be complex. + +--- + +## Design + +Given the goals and the proposed approaches, Approach #4 has significant advantages and fewer significant drawbacks. +Using the LMAX Disruptor offers low latency and CPU consumption via a well-maintained and tested API. The RingBuffer +intermediate data structure should serve to decouple the producer bidirectional stream from the consumer bidirectional +streams. Please see the following Entities section and Diagrams for a visual representation of the design. + +### Producer Registration Flow + +At boot time, the `BlockItemStreamService` will initialize the `StreamMediator` with the LMAX Disruptor RingBuffer. + +When a producer calls the `StreamSink` gRPC method, the `BlockItemStreamService` will create a new +`ProducerBlockItemObserver` instance for Helidon to invoke during the lifecycle of the bidirectional connection to the +upstream producer. The `ProducerBlockItemObserver` is constructed with a reference to the `StreamMediator` and to +the `ResponseStreamObserver` managed by Helidon for transmitting BlockItemResponses to the producer. +See the Producer Registration Flow diagram for more details. + +### Consumer Registration Flow + +When a consumer calls the `StreamSource` gRPC method, the `BlockItemStreamService` will create a new +`ConsumerBlockItemObserver` instance for Helidon to invoke during the lifecycle of the bidirectional connection to the +downstream consumer. The `ConsumerBlockItemObserver` is constructed with a reference to the `StreamMediator` and to +the `ResponseStreamObserver` managed by Helidon for transmitting BlockItemResponses to the downstream consumer. The +`BlockItemStreamService` will also subscribe the `ConsumerBlockItemObserver` to the `StreamMediator` to receive the +streaming BlockItems from the producer. + + +### Runtime Streaming + +At runtime, the `ProducerBlockItemObserver` will receive the latest BlockItem from the producer via Helidon and will +invoke publishEvent(BlockItem) on the `StreamMediator` to write the BlockItem to the RingBuffer. The +`ProducerBlockItemObserver` will then persist the BlockItem and return a BlockItemResponse to the producer via +its reference to `ResponseStreamObserver`. + +Asynchronously, the RingBuffer will invoke the onEvent(BlockItem) method of all the subscribed +`ConsumerBlockItemObserver`s passing them the latest BlockItem. The `ConsumerBlockItemObserver` will then transmit +the BlockItem downstream to the consumer via its reference to the `ResponseStreamObserver`. Downstream consumers will +respond with a BlockItemResponse. Helidon will call the onNext() method of the `ConsumerBlockItemObserver` with the +BlockItemResponse. + +BlockItems sent to the `ConsumerBlockItemObserver` via the RingBuffer and BlockItemResponses passed by Helidon from +the downstream consumer are used to refresh internal timeouts maintained by the `ConsumerBlockItemObserver`. If a +configurable timeout threshold is exceeded, the `ConsumerBlockItemObserver` will unsubscribe itself from the +`StreamMediator`. This mechanism is necessary because producers and consumers may not send HTTP/2 `End Stream` DATA +frames to terminate their bidirectional connection. Moreover, Helidon does not throw an exception back up to +`ConsumerBlockItemObserver` when the downstream consumer disconnects. Internal timeouts ensure objects are not +permanently subscribed to the `StreamMediator`. + +### Entities + +**BlockItemStreamService** - The BlockItemStreamService is a custom implementation of the Helidon gRPC GrpcService. +It is responsible for initializing the StreamMediator and instantiating ProducerBlockItemObserver and +ConsumerBlockItemObserver instances on-demand when the gRPC API is called by producers and consumers. It is +the primary binding between the Helidon routing mechanisms and the `hedera-block-node` custom business logic. + +**StreamObserver** - StreamObserver is the main interface through which Helidon 4.x.x invokes custom business logic +to receive and transmit bidirectional BlockItem streams at runtime. + +**ProducerBlockItemObserver** - A custom implementation of StreamObserver invoked by Helidon at runtime which is +responsible for: +1) Receiving the latest BlockItem from the producer (e.g. Consensus Node). +2) Returning a response to the producer. + +**StreamMediator** - StreamMediator is an implementation of the [Mediator Pattern](https://en.wikipedia.org/wiki/Mediator_pattern) +encapsulating the communication and interaction between the producer (ProducerBlockItemObserver) and N consumers +(ConsumerBlockItemObserver) using the RingBuffer of the Disruptor library. It manages the 1-to-N relationship between +the producer and consumers. + +**RingBuffer** - A shared data structure between the producer and consumers that temporarily stores inbound BlockItems. +The RingBuffer is a fixed-sized array of ConsumerBlockItemObservers that is managed by the Disruptor library. + +**EventHandler** - The EventHandler is an integration interface provided by the Disruptor library as a mechanism to +invoke callback logic when a new BlockItem is written to the RingBuffer. The EventHandler is responsible for passing +the latest BlockItem to the ConsumerBlockItemObserver when it is available in the RingBuffer. + +**ConsumerBlockItemObserver** - A custom implementation of StreamObserver called by Helidon which is responsible for: +1) Receiving the latest response from the downstream consumer. +2) Receiving the latest BlockItem from the RingBuffer. +3) Sending the latest BlockItem to the downstream consumer. + +**BlockPersistenceHandler** - The BlockPersistenceHandler is responsible for writing the latest BlockItem to disk. + +--- +## Diagrams + + +### Producer Registration Flow + +![Producer Registration](assets/00036-producer-registration.png) + + +### Consumer Registration Flow + +![Consumer Registration](assets/00036-consumer-registration.png) + + +### Class Diagram of all Entities and their Relationships + +![Class Diagram](assets/00036-demo-disruptor-class-diagram.png) + +### Runtime Stream of BlockItems from Producer to Consumers + +![Sequence Diagram](assets/00036-refactor-demo-disruptor.png) + + +--- +