PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/saltstack/salt/lib/python3.10/site-packages/salt/transport
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/transport/ipc.py
""" IPC transport classes """ import datetime import errno import logging import socket import time import salt.defaults import salt.ext.tornado import salt.ext.tornado.concurrent import salt.ext.tornado.gen import salt.ext.tornado.ioloop import salt.ext.tornado.netutil import salt.transport.frame import salt.utils.msgpack from salt.ext.tornado.ioloop import IOLoop from salt.ext.tornado.ioloop import TimeoutError as TornadoTimeoutError from salt.ext.tornado.iostream import IOStream, StreamClosedError from salt.ext.tornado.locks import Lock log = logging.getLogger(__name__) # 'tornado.concurrent.Future' doesn't support # remove_done_callback() which we would have called # in the timeout case. Due to this, we have this # callback function outside of FutureWithTimeout. def future_with_timeout_callback(future): if future._future_with_timeout is not None: future._future_with_timeout._done_callback(future) class FutureWithTimeout(salt.ext.tornado.concurrent.Future): def __init__(self, io_loop, future, timeout): super().__init__() self.io_loop = io_loop self._future = future if timeout is not None: if timeout < 0.1: timeout = 0.1 self._timeout_handle = self.io_loop.add_timeout( self.io_loop.time() + timeout, self._timeout_callback ) else: self._timeout_handle = None if hasattr(self._future, "_future_with_timeout"): # Reusing a future that has previously been used. # Due to this, no need to call add_done_callback() # because we did that before. self._future._future_with_timeout = self if self._future.done(): future_with_timeout_callback(self._future) else: self._future._future_with_timeout = self self._future.add_done_callback(future_with_timeout_callback) def _timeout_callback(self): self._timeout_handle = None # 'tornado.concurrent.Future' doesn't support # remove_done_callback(). So we set an attribute # inside the future itself to track what happens # when it completes. self._future._future_with_timeout = None self.set_exception(TornadoTimeoutError()) def _done_callback(self, future): try: if self._timeout_handle is not None: self.io_loop.remove_timeout(self._timeout_handle) self._timeout_handle = None self.set_result(future.result()) except Exception as exc: # pylint: disable=broad-except self.set_exception(exc) class IPCServer: """ A Tornado IPC server very similar to Tornado's TCPServer class but using either UNIX domain sockets or TCP sockets """ async_methods = [ "handle_stream", ] close_methods = [ "close", ] def __init__(self, socket_path, io_loop=None, payload_handler=None): """ Create a new Tornado IPC server :param str/int socket_path: Path on the filesystem for the socket to bind to. This socket does not need to exist prior to calling this method, but parent directories should. It may also be of type 'int', in which case it is used as the port for a tcp localhost connection. :param IOLoop io_loop: A Tornado ioloop to handle scheduling :param func payload_handler: A function to customize handling of incoming data. """ self.socket_path = socket_path self._started = False self.payload_handler = payload_handler # Placeholders for attributes to be populated by method calls self.sock = None self.io_loop = io_loop or salt.ext.tornado.ioloop.IOLoop.current() self._closing = False def start(self): """ Perform the work necessary to start up a Tornado IPC server Blocks until socket is established """ # Start up the ioloop log.trace("IPCServer: binding to socket: %s", self.socket_path) if isinstance(self.socket_path, int): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.setblocking(0) self.sock.bind(("127.0.0.1", self.socket_path)) # Based on default used in tornado.netutil.bind_sockets() self.sock.listen(128) else: self.sock = salt.ext.tornado.netutil.bind_unix_socket(self.socket_path) with salt.utils.asynchronous.current_ioloop(self.io_loop): salt.ext.tornado.netutil.add_accept_handler( self.sock, self.handle_connection, ) self._started = True @salt.ext.tornado.gen.coroutine def handle_stream(self, stream): """ Override this to handle the streams as they arrive :param IOStream stream: An IOStream for processing See https://tornado.readthedocs.io/en/latest/iostream.html#tornado.iostream.IOStream for additional details. """ @salt.ext.tornado.gen.coroutine def _null(msg): raise salt.ext.tornado.gen.Return(None) def write_callback(stream, header): if header.get("mid"): @salt.ext.tornado.gen.coroutine def return_message(msg): pack = salt.transport.frame.frame_msg_ipc( msg, header={"mid": header["mid"]}, raw_body=True, ) yield stream.write(pack) return return_message else: return _null unpacker = salt.utils.msgpack.Unpacker(raw=False) while not stream.closed(): try: wire_bytes = yield stream.read_bytes(4096, partial=True) unpacker.feed(wire_bytes) for framed_msg in unpacker: body = framed_msg["body"] self.io_loop.spawn_callback( self.payload_handler, body, write_callback(stream, framed_msg["head"]), ) except StreamClosedError: log.trace("Client disconnected from IPC %s", self.socket_path) break except OSError as exc: # On occasion an exception will occur with # an error code of 0, it's a spurious exception. if exc.errno == 0: log.trace( "Exception occurred with error number 0, " "spurious exception: %s", exc, ) else: log.error("Exception occurred while handling stream: %s", exc) except Exception as exc: # pylint: disable=broad-except log.error("Exception occurred while handling stream: %s", exc) def handle_connection(self, connection, address): log.trace( "IPCServer: Handling connection to address: %s", address if address else connection, ) try: with salt.utils.asynchronous.current_ioloop(self.io_loop): stream = IOStream( connection, ) self.io_loop.spawn_callback(self.handle_stream, stream) except Exception as exc: # pylint: disable=broad-except log.error("IPC streaming error: %s", exc) def close(self): """ Routines to handle any cleanup before the instance shuts down. Sockets and filehandles should be closed explicitly, to prevent leaks. """ if self._closing: return self._closing = True if hasattr(self.sock, "close"): self.sock.close() # pylint: disable=W1701 def __del__(self): try: self.close() except TypeError: # This is raised when Python's GC has collected objects which # would be needed when calling self.close() pass # pylint: enable=W1701 def __enter__(self): return self def __exit__(self, *args): self.close() class IPCClient: """ A Tornado IPC client very similar to Tornado's TCPClient class but using either UNIX domain sockets or TCP sockets This was written because Tornado does not have its own IPC server/client implementation. :param IOLoop io_loop: A Tornado ioloop to handle scheduling :param str/int socket_path: A path on the filesystem where a socket belonging to a running IPCServer can be found. It may also be of type 'int', in which case it is used as the port for a tcp localhost connection. """ def __init__(self, socket_path, io_loop=None): """ Create a new IPC client IPC clients cannot bind to ports, but must connect to existing IPC servers. Clients can then send messages to the server. """ self.io_loop = io_loop or salt.ext.tornado.ioloop.IOLoop.current() self.socket_path = socket_path self._closing = False self.stream = None self.unpacker = salt.utils.msgpack.Unpacker(raw=False) self._connecting_future = None def connected(self): return self.stream is not None and not self.stream.closed() def connect(self, callback=None, timeout=None): """ Connect to the IPC socket """ if self._connecting_future is not None and not self._connecting_future.done(): future = self._connecting_future else: if self._connecting_future is not None: # read previous future result to prevent the "unhandled future exception" error self._connecting_future.exception() # pylint: disable=E0203 future = salt.ext.tornado.concurrent.Future() self._connecting_future = future self._connect(timeout) if callback is not None: def handle_future(future): response = future.result() self.io_loop.add_callback(callback, response) future.add_done_callback(handle_future) return future @salt.ext.tornado.gen.coroutine def _connect(self, timeout=None): """ Connect to a running IPCServer """ if isinstance(self.socket_path, int): sock_type = socket.AF_INET sock_addr = ("127.0.0.1", self.socket_path) else: sock_type = socket.AF_UNIX sock_addr = self.socket_path self.stream = None if timeout is not None: timeout_at = time.time() + timeout while True: if self._closing: break if self.stream is None: with salt.utils.asynchronous.current_ioloop(self.io_loop): self.stream = IOStream(socket.socket(sock_type, socket.SOCK_STREAM)) try: log.trace("IPCClient: Connecting to socket: %s", self.socket_path) yield self.stream.connect(sock_addr) self._connecting_future.set_result(True) break except Exception as e: # pylint: disable=broad-except if self.stream.closed(): self.stream = None if timeout is None or time.time() > timeout_at: if self.stream is not None: self.stream.close() self.stream = None self._connecting_future.set_exception(e) break yield salt.ext.tornado.gen.sleep(1) def close(self): """ Routines to handle any cleanup before the instance shuts down. Sockets and filehandles should be closed explicitly, to prevent leaks. """ if self._closing: return self._closing = True self._connecting_future = None log.debug("Closing %s instance", self.__class__.__name__) if self.stream is not None and not self.stream.closed(): try: self.stream.close() except OSError as exc: if exc.errno != errno.EBADF: # If its not a bad file descriptor error, raise raise # pylint: disable=W1701 def __del__(self): try: self.close() except TypeError: # This is raised when Python's GC has collected objects which # would be needed when calling self.close() pass # pylint: enable=W1701 def __enter__(self): return self def __exit__(self, *args): self.close() class IPCMessageClient(IPCClient): """ Salt IPC message client Create an IPC client to send messages to an IPC server An example of a very simple IPCMessageClient connecting to an IPCServer. This example assumes an already running IPCMessage server. IMPORTANT: The below example also assumes a running IOLoop process. # Import Tornado libs import salt.ext.tornado.ioloop # Import Salt libs import salt.config import salt.transport.ipc io_loop = salt.ext.tornado.ioloop.IOLoop.current() ipc_server_socket_path = '/var/run/ipc_server.ipc' ipc_client = salt.transport.ipc.IPCMessageClient(ipc_server_socket_path, io_loop=io_loop) # Connect to the server ipc_client.connect() # Send some data ipc_client.send('Hello world') """ async_methods = [ "send", "connect", "_connect", ] close_methods = [ "close", ] # FIXME timeout unimplemented # FIXME tries unimplemented @salt.ext.tornado.gen.coroutine def send(self, msg, timeout=None, tries=None): """ Send a message to an IPC socket If the socket is not currently connected, a connection will be established. :param dict msg: The message to be sent :param int timeout: Timeout when sending message (Currently unimplemented) """ if not self.connected(): yield self.connect() pack = salt.transport.frame.frame_msg_ipc(msg, raw_body=True) yield self.stream.write(pack) class IPCMessageServer(IPCServer): """ Salt IPC message server Creates a message server which can create and bind to a socket on a given path and then respond to messages asynchronously. An example of a very simple IPCServer which prints received messages to a console: # Import Tornado libs import salt.ext.tornado.ioloop # Import Salt libs import salt.transport.ipc io_loop = salt.ext.tornado.ioloop.IOLoop.current() ipc_server_socket_path = '/var/run/ipc_server.ipc' ipc_server = salt.transport.ipc.IPCMessageServer(ipc_server_socket_path, io_loop=io_loop, payload_handler=print_to_console) # Bind to the socket and prepare to run ipc_server.start() # Start the server io_loop.start() # This callback is run whenever a message is received def print_to_console(payload): print(payload) See IPCMessageClient() for an example of sending messages to an IPCMessageServer instance """ class IPCMessagePublisher: """ A Tornado IPC Publisher similar to Tornado's TCPServer class but using either UNIX domain sockets or TCP sockets """ def __init__(self, opts, socket_path, io_loop=None): """ Create a new Tornado IPC server :param dict opts: Salt options :param str/int socket_path: Path on the filesystem for the socket to bind to. This socket does not need to exist prior to calling this method, but parent directories should. It may also be of type 'int', in which case it is used as the port for a tcp localhost connection. :param IOLoop io_loop: A Tornado ioloop to handle scheduling """ self.opts = opts self.socket_path = socket_path self._started = False # Placeholders for attributes to be populated by method calls self.sock = None self.io_loop = io_loop or IOLoop.current() self._closing = False self.streams = set() def start(self): """ Perform the work necessary to start up a Tornado IPC server Blocks until socket is established """ # Start up the ioloop log.trace("IPCMessagePublisher: binding to socket: %s", self.socket_path) if isinstance(self.socket_path, int): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.setblocking(0) self.sock.bind(("127.0.0.1", self.socket_path)) # Based on default used in salt.ext.tornado.netutil.bind_sockets() self.sock.listen(128) else: self.sock = salt.ext.tornado.netutil.bind_unix_socket(self.socket_path) with salt.utils.asynchronous.current_ioloop(self.io_loop): salt.ext.tornado.netutil.add_accept_handler( self.sock, self.handle_connection, ) self._started = True @salt.ext.tornado.gen.coroutine def _write(self, stream, pack): timeout = self.opts.get("ipc_write_timeout", salt.defaults.IPC_WRITE_TIMEOUT) try: yield salt.ext.tornado.gen.with_timeout( datetime.timedelta(seconds=timeout), stream.write(pack), quiet_exceptions=(StreamClosedError,), ) except salt.ext.tornado.gen.TimeoutError: log.trace("Failed to relay event to client after %d seconds", timeout) if not stream.closed(): stream.close() self.streams.discard(stream) except StreamClosedError: log.trace("Client disconnected from IPC %s", self.socket_path) self.streams.discard(stream) except Exception as exc: # pylint: disable=broad-except log.error("Exception occurred while handling stream: %s", exc) if not stream.closed(): stream.close() self.streams.discard(stream) def publish(self, msg): """ Send message to all connected sockets """ if not self.streams: return pack = salt.transport.frame.frame_msg_ipc(msg, raw_body=True) for stream in self.streams: self.io_loop.spawn_callback(self._write, stream, pack) def handle_connection(self, connection, address): log.trace("IPCServer: Handling connection to address: %s", address) try: kwargs = {} if self.opts["ipc_write_buffer"] > 0: kwargs["max_write_buffer_size"] = self.opts["ipc_write_buffer"] log.trace( "Setting IPC connection write buffer: %s", (self.opts["ipc_write_buffer"]), ) with salt.utils.asynchronous.current_ioloop(self.io_loop): stream = IOStream(connection, **kwargs) self.streams.add(stream) def discard_after_closed(): self.streams.discard(stream) stream.set_close_callback(discard_after_closed) except Exception as exc: # pylint: disable=broad-except log.error("IPC streaming error: %s", exc) def close(self): """ Routines to handle any cleanup before the instance shuts down. Sockets and filehandles should be closed explicitly, to prevent leaks. """ if self._closing: return self._closing = True for stream in self.streams: stream.close() self.streams.clear() if hasattr(self.sock, "close"): self.sock.close() def __enter__(self): return self def __exit__(self, *args): self.close() class IPCMessageSubscriber(IPCClient): """ Salt IPC message subscriber Create an IPC client to receive messages from IPC publisher An example of a very simple IPCMessageSubscriber connecting to an IPCMessagePublisher. This example assumes an already running IPCMessagePublisher. IMPORTANT: The below example also assumes the IOLoop is NOT running. # Import Tornado libs import salt.ext.tornado.ioloop # Import Salt libs import salt.config import salt.transport.ipc # Create a new IO Loop. # We know that this new IO Loop is not currently running. io_loop = salt.ext.tornado.ioloop.IOLoop() ipc_publisher_socket_path = '/var/run/ipc_publisher.ipc' ipc_subscriber = salt.transport.ipc.IPCMessageSubscriber(ipc_server_socket_path, io_loop=io_loop) # Connect to the server # Use the associated IO Loop that isn't running. io_loop.run_sync(ipc_subscriber.connect) # Wait for some data package = ipc_subscriber.read_sync() """ async_methods = [ "read", "connect", ] close_methods = [ "close", ] def __init__(self, socket_path, io_loop=None): super().__init__(socket_path, io_loop=io_loop) self._read_stream_future = None self._saved_data = [] self._read_in_progress = Lock() @salt.ext.tornado.gen.coroutine def _read(self, timeout, callback=None): try: try: yield self._read_in_progress.acquire(timeout=0.00000001) except salt.ext.tornado.gen.TimeoutError: raise salt.ext.tornado.gen.Return(None) exc_to_raise = None ret = None try: while True: if self._read_stream_future is None: self._read_stream_future = self.stream.read_bytes( 4096, partial=True ) if timeout is None: wire_bytes = yield self._read_stream_future else: wire_bytes = yield FutureWithTimeout( self.io_loop, self._read_stream_future, timeout ) self._read_stream_future = None # Remove the timeout once we get some data or an exception # occurs. We will assume that the rest of the data is already # there or is coming soon if an exception doesn't occur. timeout = None self.unpacker.feed(wire_bytes) first_sync_msg = True for framed_msg in self.unpacker: if callback: self.io_loop.spawn_callback(callback, framed_msg["body"]) elif first_sync_msg: ret = framed_msg["body"] first_sync_msg = False else: self._saved_data.append(framed_msg["body"]) if not first_sync_msg: # We read at least one piece of data and we're on sync run break except TornadoTimeoutError: # In the timeout case, just return None. # Keep 'self._read_stream_future' alive. ret = None except StreamClosedError as exc: log.trace("Subscriber disconnected from IPC %s", self.socket_path) self._read_stream_future = None except Exception as exc: # pylint: disable=broad-except log.error( "Exception occurred in Subscriber while handling stream: %s", exc, exc_info_on_level=logging.DEBUG, ) self._read_stream_future = None exc_to_raise = exc self._read_in_progress.release() if exc_to_raise is not None: raise exc_to_raise # pylint: disable=E0702 raise salt.ext.tornado.gen.Return(ret) # Handle ctrl+c gracefully except TypeError: pass @salt.ext.tornado.gen.coroutine def read(self, timeout): """ Asynchronously read messages and invoke a callback when they are ready. :param callback: A callback with the received data """ if self._saved_data: res = self._saved_data.pop(0) raise salt.ext.tornado.gen.Return(res) while not self.connected(): try: yield self.connect(timeout=5) except StreamClosedError: log.trace( "Subscriber closed stream on IPC %s before connect", self.socket_path, ) yield salt.ext.tornado.gen.sleep(1) except Exception as exc: # pylint: disable=broad-except log.error("Exception occurred while Subscriber connecting: %s", exc) yield salt.ext.tornado.gen.sleep(1) res = yield self._read(timeout) raise salt.ext.tornado.gen.Return(res) def read_sync(self, timeout=None): """ Read a message from an IPC socket The socket must already be connected. The associated IO Loop must NOT be running. :param int timeout: Timeout when receiving message :return: message data if successful. None if timed out. Will raise an exception for all other error conditions. """ if self._saved_data: return self._saved_data.pop(0) return self.io_loop.run_sync(lambda: self._read(timeout)) @salt.ext.tornado.gen.coroutine def read_async(self, callback): """ Asynchronously read messages and invoke a callback when they are ready. :param callback: A callback with the received data """ while not self.connected(): try: yield self.connect(timeout=5) except StreamClosedError: log.trace( "Subscriber closed stream on IPC %s before connect", self.socket_path, ) yield salt.ext.tornado.gen.sleep(1) except Exception as exc: # pylint: disable=broad-except log.error("Exception occurred while Subscriber connecting: %s", exc) yield salt.ext.tornado.gen.sleep(1) yield self._read(None, callback) def close(self): """ Routines to handle any cleanup before the instance shuts down. Sockets and filehandles should be closed explicitly, to prevent leaks. """ if self._closing: return super().close() # This will prevent this message from showing up: # '[ERROR ] Future exception was never retrieved: # StreamClosedError' if self._read_stream_future is not None and self._read_stream_future.done(): exc = self._read_stream_future.exception() if exc and not isinstance(exc, StreamClosedError): log.error("Read future returned exception %r", exc)