From 9b923ebe49b5779910897355d9d72c63e2102681 Mon Sep 17 00:00:00 2001 From: milk <53408947@qq.com> Date: Wed, 29 Oct 2025 16:56:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/AnchorModel.cpython-312.pyc | Bin 1111 -> 1114 bytes .../__pycache__/DeviceModel.cpython-312.pyc | Bin 1155 -> 1158 bytes Entity/__pycache__/ResultData.cpython-312.pyc | Bin 1028 -> 1031 bytes Entity/__pycache__/Variables.cpython-312.pyc | Bin 3197 -> 3227 bytes Module/FlaskService.py | 3 - Module/FlaskSubprocessManager.py | 1 - Module/Main.py | 17 +- Module/__pycache__/DeviceInfo.cpython-312.pyc | Bin 27150 -> 27289 bytes .../__pycache__/FlaskService.cpython-312.pyc | Bin 39534 -> 39646 bytes .../FlaskSubprocessManager.cpython-312.pyc | Bin 13158 -> 13260 bytes Module/__pycache__/Main.cpython-312.pyc | Bin 3735 -> 3763 bytes Utils/LogManager.py | 2 +- Utils/ThreadManager.py | 180 ++++++++++++++---- Utils/__pycache__/AiUtils.cpython-312.pyc | Bin 50190 -> 50335 bytes .../__pycache__/ControlUtils.cpython-312.pyc | Bin 13079 -> 13101 bytes .../DevDiskImageDeployer.cpython-312.pyc | Bin 7142 -> 7157 bytes Utils/__pycache__/JsonUtils.cpython-312.pyc | Bin 11386 -> 11658 bytes Utils/__pycache__/LogManager.cpython-312.pyc | Bin 14626 -> 14663 bytes Utils/__pycache__/Requester.cpython-312.pyc | Bin 6252 -> 6260 bytes .../__pycache__/ThreadManager.cpython-312.pyc | Bin 9678 -> 15186 bytes .../__pycache__/ScriptManager.cpython-312.pyc | Bin 66388 -> 66408 bytes 21 files changed, 158 insertions(+), 45 deletions(-) diff --git a/Entity/__pycache__/AnchorModel.cpython-312.pyc b/Entity/__pycache__/AnchorModel.cpython-312.pyc index e0c2ef22ac86a327f513e253695b1d2987dadd97..18285a94171454a16e6694f29eaa5e6dee0dee1c 100644 GIT binary patch delta 55 zcmcc4af^fZG%qg~0}#mGx}2fBk(ZHCRLj*WCOJPPH7204BqKjBCeuIIF>|v5BPTQC JugO|0x&Xj@5I_I` delta 52 zcmcb`ah-$rG%qg~0}xCMW5~4F$jitmr0!}JQ&3rwk)Idi=^q>)@94Q%nURy3@z!Kr G7F_^q@D2I^ diff --git a/Entity/__pycache__/DeviceModel.cpython-312.pyc b/Entity/__pycache__/DeviceModel.cpython-312.pyc index f9b0b11f1e1dd62a9b87d684f202174cac450baa..a45103065bfc380f9b45dd56540b298e0fe01f8f 100644 GIT binary patch delta 55 zcmZqXY~$oT&CAQh00gqPE@u>N)@94St7h@a~J&CAQh00a}m7&1R@F8-NsS4pEXl~vi^=p4cFdgY#$ll~67IV5MOUg1#q&I}a#4iX1TZl2F!&M4-N(7?>9d_!3B69W^c i^5jRHNjk9Fj9suW%@QXJ+7%=$Gr1+q{;;oRP;Irs}q^pq_#kg$ zG+3MmmJmaqjSrAD@eA;R#-vTb?t_VZ0$G|Sn>5CFw#3_JGru`A=gge*pR;y=HxDuY zi4Y#a7rVR5k20>hcywZFVfy~W!1B_=h53ou{-Lf}Wsyl+aGu8aY3qwyd1NuW=vzJ_ z0Sk5{IFrq?DNFo$WP*)j-pc?LQr2p9zQG8sAeA^?jXxhhL6{rBG$GcTN|O~aa*MpQtm$uR0~!lye6&@H>M$cxQN*Qzf5nhCK}#4h?NO11wmO-7gOw+NxBSXJdTb zNYv4wO75Ign~`dBQZOS0>73Kr4a-Lr?$@klgWI5Vn>X!^WrcW}jqCMBO{XOmbvxu( zO1-F`{e4|gXCYpB=WM}Z6DamX9tI3}!=x!TL{D!pFa)~a3#een#X z26r4Eyu02#A=jD6bSAQm2|2J`vsIJ!3?(fvfFe#m4X$>pE^i)6tfTL%Yp(${7K!Dj zdScS})$NsI+r68;IbSH_3uS%bth*s;)*0B-EXY%pa%~ErDCxqK1p1uDq#z=XUmn>i z%Ojq|FntghH5`9;lr|lIO%uk9%sBdp+L>**|>@5t@kQ z=#AmaY}VDIA(P_*T^^4115XU&sG>tR0}DKY-sIPL`O1O4xjr`!i(on~ga00_ki iNkLwcXHqxQLim7fP;HWKgUHgE3Lcq|K%CpNJHG%hybMSH delta 700 zcmX|8O=uHA6rR~l({#JpB%9qNyGfd#N)p;wt8HVO5@@MODQKlXA-YtGqR>LEf>IC< zdJt?;+SDm+MA06jr^H3PdGaD!$da^K+k@4E=s`>|5GWp;4dO5`Z|0l#-uHd)@its8 zLF$9iNCCV&3wbIiVslGuP&)M$S%5PY;gB3uHkLzoTL5GO>Ify`mx}z}Ty1 zz*y5=8QqT2js8$h5-=^Y*xcJ_oP_|~1**}qB0Z4M6}BBi4z$Kbh;Ffc0bYrl7On zJXK`k1w*{nju|dVhcz>!zkXWsdaDG!V*1LMWx;~C%cz>`3> zHLWiQ6Ij00jlzpVd%P3rd?6axTonUgZJG@z<`XnR6+I9e28*biR%c_D}!Av;8pxF8giB z3*i!Kj4KVlyD)$BHNHh@I$8iH9GKC?cu4+|_-r)1ty{UrqOHjevcxA4nT(o&Z5S$i kV&5LaPK@)N2B&o;1lx0=zH>28{%l3c{^$hbS%8B0L^6&s-V3V8_&|#irZ;G6P z5r@=hHMJA7Q4ori(;#6{)1D;29ImF-R}zOgG9+RgSd_>o92&*A4$0}ThOeJPT%)WT zeLT3Ri{LQO_-+BY@jlob^w*@bOC6(PWOOj=#(PZgqb?c1A1Pk z-9QyOeGF8o(|w>Sozg{{FiWQzpxHVRpgB5S0-CGSLm*kFtRR~(Pp4X-YMt7F=IeAB zXn{_TfNFFaWwZ$kb*ck$>9pTy3tgn9Dz!^f5z=BHe~CRU`i-&FU`}`DSJyTwYW0lO zE0#1iRJ*5FjCYe9lOxRmPr4}$egG}#4-sXI?hbVxa-CXp(SEwwhuei#!B>Ah%zic0 zNqwdg9v9IYVc8g$Qz^o3zz;}9v|pUbTAU=qVk$FwP3F5u3eM~`#~z3!^S71UPR{JM zmLCc~VLy{}GU=?;l|Av|s;=y^eU7imn=yV1c>Qd_KLa9)n~v`XJxS1x*dv0~%~~H0 zOy`GZ>%={#@Vm$oT!rbpA;DN;${%3jPMG`@($fhML3lg;EV&~n=Y7`Y%Hu9lyzqER zE;#>oLP~dJ-mCUw`6pcO7PO7O8kx6k5`_B#6zRdt`Pc~JE@Jd3Ydyx@biG5txR2h- zj^Xef`fzj)7W-NEsckuS(w`SbrQDA=@o$zo0x8;`=j25rY~6IWEhme*3$Ah4N(+m} z^EidJOzhyqTvlvi-l-*t9G*xUr!)xSG?uL)x5_>=ejjBWKtaKyNJHf?@=19n&-8`L z8MI`2vq5ZN#tG!}io;JE%bAH@s4T^J50z%sad$%*fn9IYNmelOyaS#1r(Qh37iGHaT@#3Fs=@}B~uCQGT z&-Ej08h+N%8xz;Qx^?xTaI(&4xX(k5G`nMsa1I#ge)pXqQG|b$reyNs^6!j2VbKGK z7c69n=HTH>4`>3PU%(71+`ATZ+ibgGPWsB6)MZX-nb4D*+AS3x3V(b2apzIzi4oT` z3br|R=WNgMS+9rKu1bZpcV&?QJCE4baCj+=Uw57pm$PPj+j64Fk&R<%#)kPEx0xeF z#j(I|;q$~bYb4pdRw9=oqR8G&HuCADDDs9RMPanU1S15Fb+`_#<2rd_8f_&lMIl*S zxfTuApJvpUN87MCX<5Asgl9C zwRB_Yw&h#OsIvJl96B4y8Vd)RXAOH1UV5QOv{XatCfBe`+r>6{orQK57LXrz%#~I{ z^*y%W9Cf~I!A4)!0KBGUm@M2m#lQsZ+!>FBwP5?1UfE^j1g#P@>GhT4ev;mL2`l7Y z>noJB-NJ=z!oUa`YpbzE+6D+Wd2MCGQKS{HeMdleU>xUdR91S_LhS(5;e(ovlT&+! z4(da;K4kAh>3t}L+}d*|VhDC(->UZJ*5-SN>49Q;bMF^?r~zl*;}JK~&w=s}CZ}IJ F_a7;bMF#)? delta 2012 zcmYk+dr(wW7y$5d?%jJ?c3p6RWqB;ZqOiO|KoLPekw8HK3w&KbTDTVhcYQ4AsDb#Z zFipXeVy1>!Gb%pFqob9k){K)=Q!+%3D3%%3j85TesPUh^bJaWU>>j?~`M&erZ|`OH z_8IQ))12u$lgWVKRlX6`|IlFy#@}{0+FP;9B3Wc4d%FVNR>|rPl7cV-8(hKe5Gh2B zjjm9)O|q%+C|8(!j5J1#O|EcvgcPC1J}$dEQi@b#U)NaoIBA?aN{UjA!6qqMjYqp2 z?ieWsBMvE6B{rfPvzZW*;$S00iF;uvHY;%r^T-B2f1_U}RLK4>Oj@ztD*@{H28mpb z4{4PW0s3yW5wse=7&((c?!JtCOb6;R@HFdp^NSf1n@& zj!l#8n~{{G&b;}h^z%^3kt2_d?dG0Q%2je3wyTE)hrV>!GrcIy{O=m%KG~ENerYT&o39oA^1(j-g z2UMo%OVC_R|A6Lc8Y|j_`I_c~7HHZETBzwFNYeBORIbV4WfLkim4hlZ?Eo#(^bM#= z(+KDdP4Rl0;M7zFs@6pGHs2a0R<2nRgOJAx`&Zf1qP_Y;onK5|M#b_qO?AtbRuq(! zR#oK`kR(%b+yr>cjdAdgzaBjiQNqMN-@GG}yPT)jUb3HQX~&&HhtS^8KiYnHGJV&S z&*L20J30m9IaKFo*Wm#Y9XKqOvK~i?%~}?J+hnQ8lbKeJ@x}u3@oOt1E5m0zi|CbbKQFwE-iZFhEBOpNb>-P9Lzs{N;|zs6`ux*B zSiQ%7EThMHJZoppp4DxyUh_{Mf%Ad%RAMD-8MtKuwI;2@;tkdzo;;Z3PfsMz#`stI zG{wq^f3a;Dw(T@M^%TYr>HSHMIsBB~NngTaBQ46(^I{lWxN*B$)I2Se7ujuX>{9(z z?m7-<(5(CdPRwW9)vz5&Pt2Slh$YMtd0KRVzZ(!jE)~z=ktLMeDVsxUN?LT{3YO0z zf0TZ#=G)7PS)Sf0E5x{yCd^yR;RE#j1;=0(EWkh*40qgU&PyhLyKmNb9CiA}8AE^>K830>4$$>F!j`3)Cw zJLyeIBjp7FWWT>5vI1&aa0}Pu4OdlP&3G|t=DPW3YB0iYn=L#R7srYo6YP1mvc(L` zfT7yiR8v>g*tCq)Z%Q+i!R^_@E}2P=Zi=CUo1S39JlONFYRLAm4yWjaZNZo$5)ZqIhqdyw!ZN^n zRxUk)JXvgvI9LYsneJusN|%!D*$;Vm!#u>XE5dgO*#?n)5XBE7$4%r*N_P$Uh2uQj lzN)jOqva7|@x$q~aQFATFOL%+@rZk6n1g6|6p7mV`TqhnMRx!I diff --git a/Module/__pycache__/FlaskSubprocessManager.cpython-312.pyc b/Module/__pycache__/FlaskSubprocessManager.cpython-312.pyc index e6c439ece5859afffda9a577cc3fe4f1c953f30e..ccf64c6d6b23637c6409f210a7f9c9e142efcd09 100644 GIT binary patch delta 669 zcmY*WUr19?7(d_6yzV-0dEI}#GsLwvH*HI1Fy_pMNi3s4B$R6)YAHm6H47gGd+23B z%yp0@1O|~$Js5|GB8VPC9~ujGANCSdP)2Nfxza;)&IF=JIn56cUp_#_ata+^HYYMY|Vj^nv{g zbT0tc^_G5=Of${{7`(?uy|c_r4%MvA$JT9SOR2hoWmmB3ij-ZE-E_qoZ6c$1n%wX4 zV}^9MCxV3>uX{%h+~cOt@=P6JNg+dh;}S!i6ZU9Hp?CWZje&urqI5i_JXkZ@Mqu_gk!ex9s+(3+op&3{9Y5oIv44*|1bDn z@#JY{*is}*VLm_qWOO^e6@M{W4UCloV-+dRvc)LL1iTVMofGy&Vw64$_&|8e>yxA$ z?C)XAX&{gk&C#o4hB_xLg&K#R0(_v)hbI9(l3!=vz&_mxT>~N9mW1XC#pg}{{2|>V QiIZ6a*qVsjw3qe%2dTTi5&!@I delta 510 zcmX?;{w$66G%qg~0}xCMW5~4J$Xm%QWaMfUQ&3rwk)Idi=^q>)@8~&sHnTThI8f1# z28J8F0{xNQk&}Ni>xeM$iGSc=5D@OK?X11PA@iLRD6rX*g^`_6bFwwJJ@YjgoypDI zMa&Gcx|4r$+b~~~HkfS7BhSnrW4Jk<=P4(zBNGDy$VkEdsP3rA;bMxc415wDQIng* z)EJ*^UMcpFku#YQDAXU-8MS$i#A`;j3ZN^B6DIdc$uM6Nv6{S5N|TvE)Ozz>DN|;) zMv&Z=$r5t*%vS|NC&$a#b1?{oFG}XTW<)9Pq&$5b3;Jv zJJj-X%C5rKWNjw6-Vl+T?lsYCs?P^z207cw+A4a&wN{q(O>7%Q6rQ&~_U?LZ$8qfVr%vNKP2)I86A~H`p+GA*ID#}%RG}cuRV5NQY=FR)Q z_ujXoy`I`l`M>k~4aA>6?}GDA-5-}?YJDIbLRc;;tE!~}!U{pIS)WZofW7Mp*DnVk zRKJ!~b)vhG`kfT$^=4OR35af)#Kh_)A3_|F;K^OlivtKz342R`HF01CNWeW>zmp6{ zBt$w$U3Wz7m{Ou-yP3+&1+n^1gCLjXtXnTLy zZa_uiXYu_4ZiqdAWc^XN!%C0@4z#*SlBD~LD0?z^yJJ7IRV{@>BzY6vk`AJ{^8;k{ z;4rTjU`tXw)U)%XU<(26v|wU4C9}JWXZDgbj)?iUfr{Q$_XQS^j~LvI;^6J*4lqd` z@bGPyJ0k?`C;jeOo{|oB?h{)BWWb$g({egKWDRjapg3#|x7HDBghPkiB@VqjietRu zm@af0aTgEk(Dr#0|4VdiM`}k~6Z{wQ{Qo0$q>xe9sb5R5QAL%9U03K3{6~5I723yI z1-3GLNSi&-Q|F)t8AJyA5Di;7-ggRjliWc+3w&5B_iAqIi}tz>>u5t>KPoq^=$xHD?XXY0Y4sRSIm*8D zZfZjJ>!=FsH7(Xt(cVH>it`YbFHZ6b(dv_1i;$}bg$9*_yH!IqlvQURiqApA+%#0D zT(KQ00$G`P^qwo)KFLVXn_Zpky?{936z4!JF`Nru_J)fQUA(p+b z`99V@(zZiaeN7p~2Wf`Y^v{=CG~U9BUBX2|$2q}_VDhz@G*e)%8Z^WAtxmG1XhQg+ zRKvT*1uZ(SQ!z`U<6PA#Rz?oQr3!P5H$Y?ej11`PFQeBZ{x0+gJMKG~Yp83~DOIUZ zjAn%qGc>e9p=7U;LZP8A7l}EKt%+6(qTR>Zv#VwNW|2(OCB7BEjLLIdnWhW? zzk3iE_mJ^3iv5aC>^^ULq1PipSJOa>xq0bFIo|XjHGXC0Cnfg161%G;9vSQv|JT|) zASLlfv!C?_&O9CO&WF%9p{e{3`qq3ZKZ*82YCbFPMfH52yw}(Aht&Lpyf+TQPIk`d F^568g9Z>)P delta 1427 zcmaJ>O>7%Q6rS$F`a>U>p1S% zkhld=K@W~d)L3)q0hK#SZ{f(L7ih$-5<)UT;z(}+4j?Ka-fUvkmI5Q~+c)p$oA13h zySuU5G0*oNkB(rq=27W-%M*6|D!0-}MWV>kT4oIE{GQg|`rL~>JT65Pm8w=BXv5>o z05u>|dshWh)zqm@Qey`nLW#3Q z5|+4yBdS*Rt@uqpzk!EmAy9&6fMVh%#*TEDu#TC55yjTYUM^y{j%yC^4RydRQONL# zT$qNfvx38d_pYy#iV++gA$}VE0^M;uwvxV^$c&Nz5U_jMOX5Ss!jKo; zIWpTQ*qRpTIn;&H4=8auLV|XyeutbO@!lQiM3@PhuwLLZQq&2qfHf-y!vAz6gD%0# z-@!M>=$Q2yuSm&o#>@~zaCzJux8GSa3y~S?9g+IUDKZ2np0=~I)_gB(v7=e*(lOT4 zJB~xEUCDoCF8zO|3(549by>`UFUg8DZV}NCU#L?q%${H*NmYNJA0qgI?i_;gY zjr#IxANWUfp1itwcSsW^K==%Vv1BZR1+ ztrZHD;tDMk_;vT4oDH9_?dMij$p>XR!xmvHrc*l!y*ZFD#(y{w)_+3!&nUF}s-a@P z?8Kpi0FsU7wFgr8H-pza_p}Qb$-{pgjPi5d=g;DyxheGR)O0?GzB}`Degxg0Qu2M$ a{TWx@koF9r?*)~7RN9MRAx8rFob)ffHx2Os diff --git a/Utils/LogManager.py b/Utils/LogManager.py index 3981133..0c8bb94 100644 --- a/Utils/LogManager.py +++ b/Utils/LogManager.py @@ -26,7 +26,7 @@ def _force_utf8_everywhere(): except Exception: pass -_force_utf8_everywhere() +# _force_utf8_everywhere() class LogManager: """ diff --git a/Utils/ThreadManager.py b/Utils/ThreadManager.py index 632cabe..a074efa 100644 --- a/Utils/ThreadManager.py +++ b/Utils/ThreadManager.py @@ -1,16 +1,23 @@ import ctypes import threading import time +import os +import signal from concurrent.futures import ThreadPoolExecutor, as_completed -from typing import Dict, Tuple, List +from typing import Dict, Tuple, List, Optional from Utils.LogManager import LogManager +try: + import psutil # 可选:用来级联杀子进程 +except Exception: + psutil = None -def _async_raise(tid: int, exc_type=KeyboardInterrupt) -> bool: - """向指定线程抛异常""" + +def _async_raise(tid: int, exc_type=SystemExit) -> bool: + """向指定线程异步注入异常(仅对 Python 解释器栈可靠)""" if not tid: - LogManager.method_error(f"强杀失败: 线程ID为空", "task") + LogManager.method_error("强杀失败: 线程ID为空", "task") return False try: res = ctypes.pythonapi.PyThreadState_SetAsyncExc( @@ -30,15 +37,70 @@ def _async_raise(tid: int, exc_type=KeyboardInterrupt) -> bool: class ThreadManager: + """ + 维持你的 add(udid, thread, event) 调用方式不变。 + - 线程统一设为 daemon + - 停止:协作 -> 多次强杀注入 -> zombie 放弃占位 + - 可选:注册并级联杀掉业务里创建的外部子进程 + """ _tasks: Dict[str, Dict] = {} _lock = threading.RLock() @classmethod def _cleanup_if_dead(cls, udid: str): obj = cls._tasks.get(udid) - if obj and not obj["thread"].is_alive(): - cls._tasks.pop(udid, None) - LogManager.method_info(f"检测到 [{udid}] 线程已结束,自动清理。", "task") + if obj: + th = obj.get("thread") + if th and not th.is_alive(): + cls._tasks.pop(udid, None) + LogManager.method_info(f"检测到 [{udid}] 线程已结束,自动清理。", "task") + + @classmethod + def register_child_pid(cls, udid: str, pid: int): + """业务里如果起了 adb/scrcpy/ffmpeg 等外部进程,请在启动后调用这个登记,便于 stop 时一起杀掉。""" + with cls._lock: + obj = cls._tasks.get(udid) + if not obj: + return + pids: set = obj.setdefault("child_pids", set()) + pids.add(int(pid)) + LogManager.method_info(f"[{udid}] 记录子进程 PID={pid}", "task") + + @classmethod + def _kill_child_pids(cls, udid: str, child_pids: Optional[set]): + if not child_pids: + return + for pid in list(child_pids): + try: + if psutil: + if psutil.pid_exists(pid): + proc = psutil.Process(pid) + # 先温柔 terminate,再等 0.5 秒,仍活则 kill;并级联子进程 + for c in proc.children(recursive=True): + try: + c.terminate() + except Exception: + pass + proc.terminate() + gone, alive = psutil.wait_procs([proc], timeout=0.5) + for a in alive: + try: + a.kill() + except Exception: + pass + else: + if os.name == "nt": + os.system(f"taskkill /PID {pid} /T /F >NUL 2>&1") + else: + try: + os.kill(pid, signal.SIGTERM) + time.sleep(0.2) + os.kill(pid, signal.SIGKILL) + except Exception: + pass + LogManager.method_info(f"[{udid}] 已尝试结束子进程 PID={pid}", "task") + except Exception as e: + LogManager.method_error(f"[{udid}] 结束子进程 {pid} 异常: {e}", "task") @classmethod def add(cls, udid: str, thread: threading.Thread, event: threading.Event, force: bool = False) -> Tuple[int, str]: @@ -51,19 +113,27 @@ class ThreadManager: LogManager.method_info(f"[{udid}] 检测到旧任务,尝试强制停止", "task") cls._force_stop_locked(udid) + # 强制守护线程,防止进程被挂死 + try: + thread.daemon = True + except Exception: + pass + try: thread.start() - # 等待 ident 初始化 - for _ in range(10): + # 等 ident 初始化 + for _ in range(20): if thread.ident: break - time.sleep(0.05) + time.sleep(0.02) cls._tasks[udid] = { "id": thread.ident, "thread": thread, "event": event, "start_time": time.time(), + "state": "running", + "child_pids": set(), } LogManager.method_info(f"创建任务成功 [{udid}],线程ID={thread.ident}", "task") return 200, "创建成功" @@ -81,6 +151,7 @@ class ThreadManager: thread = obj["thread"] event = obj["event"] tid = obj["id"] + child_pids = set(obj.get("child_pids") or []) LogManager.method_info(f"请求停止 [{udid}] 线程ID={tid}", "task") @@ -88,56 +159,101 @@ class ThreadManager: cls._tasks.pop(udid, None) return 200, "已结束" - # 协作式停止 + obj["state"] = "stopping" + + # 先把 event 打开,给协作退出的机会 try: event.set() except Exception as e: LogManager.method_error(f"[{udid}] 设置停止事件失败: {e}", "task") def _wait_stop(): - # 先给 1 秒高频检查机会(很多 I/O 点会在这个窗口立刻感知到) + # 高频窗口 1s(很多 I/O 点会在这个窗口立刻感知到) t0 = time.time() while time.time() - t0 < 1.0 and thread.is_alive(): time.sleep(0.05) - # 再进入原有的 join 窗口 - thread.join(timeout=stop_timeout) - if thread.is_alive(): - LogManager.method_info(f"[{udid}] 协作超时 -> 尝试强杀", "task") - try: - _async_raise(tid) # 兜底:依然保留你的策略 - except Exception as e: - LogManager.method_error(f"[{udid}] 强杀触发失败: {e}", "task") - thread.join(timeout=kill_timeout) + # 子进程先收拾(避免后台外部程序继续卡死) + cls._kill_child_pids(udid, child_pids) - if not thread.is_alive(): - LogManager.method_info(f"[{udid}] 停止成功", "task") - else: - LogManager.method_error(f"[{udid}] 停止失败(线程卡死),已清理占位", "task") + # 正常 join 窗口 + if thread.is_alive(): + thread.join(timeout=stop_timeout) + + # 仍活着 → 多次注入 SystemExit + if thread.is_alive(): + LogManager.method_info(f"[{udid}] 协作超时 -> 尝试强杀注入", "task") + for i in range(6): + ok = _async_raise(tid, SystemExit) + # 给解释器一些调度时间 + time.sleep(0.06) + if not thread.is_alive(): + break + + # 最后等待 kill_timeout + if thread.is_alive(): + thread.join(timeout=kill_timeout) with cls._lock: - cls._tasks.pop(udid, None) + if not thread.is_alive(): + LogManager.method_info(f"[{udid}] 停止成功", "task") + cls._tasks.pop(udid, None) + else: + # 彻底杀不掉:标记 zombie、释放占位 + LogManager.method_error(f"[{udid}] 停止失败(线程卡死),标记为 zombie,释放占位", "task") + obj = cls._tasks.get(udid) + if obj: + obj["state"] = "zombie" + cls._tasks.pop(udid, None) threading.Thread(target=_wait_stop, daemon=True).start() return 200, "停止请求已提交" @classmethod def _force_stop_locked(cls, udid: str): + """持锁情况下的暴力停止(用于 add(force=True) 覆盖旧任务)""" obj = cls._tasks.get(udid) if not obj: return + th = obj["thread"] + tid = obj["id"] + event = obj["event"] + child_pids = set(obj.get("child_pids") or []) try: - event = obj["event"] - event.set() - obj["thread"].join(timeout=2) - if obj["thread"].is_alive(): - _async_raise(obj["id"]) - obj["thread"].join(timeout=1) + try: + event.set() + except Exception: + pass + cls._kill_child_pids(udid, child_pids) + th.join(timeout=1.5) + if th.is_alive(): + for _ in range(6): + _async_raise(tid, SystemExit) + time.sleep(0.05) + if not th.is_alive(): + break + th.join(timeout=0.8) except Exception as e: LogManager.method_error(f"[{udid}] 强制停止失败: {e}", "task") finally: cls._tasks.pop(udid, None) + @classmethod + def status(cls, udid: str) -> Dict: + with cls._lock: + obj = cls._tasks.get(udid) + if not obj: + return {"exists": False} + o = { + "exists": True, + "state": obj.get("state"), + "start_time": obj.get("start_time"), + "thread_id": obj.get("id"), + "alive": obj["thread"].is_alive(), + "child_pids": list(obj.get("child_pids") or []), + } + return o + @classmethod def batch_stop(cls, ids: List[str]) -> Tuple[int, str]: failed = [] diff --git a/Utils/__pycache__/AiUtils.cpython-312.pyc b/Utils/__pycache__/AiUtils.cpython-312.pyc index 2be758a5be654cf1bed184c6c34a823440f7e921..5454b6e1312a84567b068a90e59f9ed4ce663366 100644 GIT binary patch delta 1296 zcmXw3Z%k8H6u ze)rtpIk_K+^vz2&_an>d5q$QMe~un{besE)vE)?kY&sNdJKR)z=Je6v$);n0`if&x zivAc!ZJ`PFS)_}$eo9ALk_fX+n6Qqr6xEU5aO6@ctGIAgdN0eEj#r78U&*Ps?hlJ|&B(o-m zQ|s$RF+R5Sme7lczvg-MGgtyjS+IYFsGk|>S!4Kg7rRHf)q&ZaSc)F0~_shPJ` zhC6S!&+uP%e8$IZl_3onInxEj@IKNP=+XfyEc`6}8z{jx;RSKb`$gfc!fDTR{fsxU zxf)Kbt-H6r8R;JDiS|r&PJ5@j6M4IPk+fz1QX0-CS zH#FYQ`gm*Hbby=-o?;UG0jQ8^!Q_u%84V68JatX4=#bT(Ln*w zxKp7hz8?QcrwE)_3F@xrHFjQdLEf?pWje!^k&Us2Xko%uB(jSAF5UQt=29);pw9vwC zI~35joCN!_S-=%FP!%tihWegkTuVOf&(qDZ#ULJ2MrQi0EZ~Y7EU89%+<%3}2@)J= z)U6er%KFH60|6RP%DmhnV2PZGoYh?qCXtrii^*S+qU`aG1!o}I7HN&~)15Q^yFKB$ z2)@=3cLqXLv(5ndCF*f3v1bm4&6TZLNtCK`oQqc2$J-a2`~K&sn6cjt ztaI!ma&+S+HE+p;j2UvaJGTF8^Yp>Hu37V*xM7c?MZ8(m$bt>#jmah6;q5L!g+rP4 z8^zbRg#hz$|7ItY`q2 zjmTsPHIPhE3m0^{=hNRJ=Or^ClvKZc~ X3M`dP{>tE0$$jUt0UoN!Z(9EYV{)nf delta 1130 zcmXw2YfM{Z82-*Vw9u}%b9y+voN}OaT!mFywl3Tvtew(KE&_unqrouQSXe3eXOhUK zAr>Xl?!$3#bL-&}iUEb3s7+=fKXi#v7i!yaDaq9Bhk?u#OM-t~Jh1uwdA~gG`@DId z=goU(1pC;FDSnd6r9gP_EV#J!a9QzKLS#G2+WW4&eZhUXy|%u|<*KgbQ>7Ob$Wh*- z{R~BKxFAJWVt~rwTj^XhWfL)P^4Bs{N@NpWFRDU^SAtv#+|;LZB}G@#E+$OR&w-cXKE z#It@u)0gIXbI2HKS}{cnPs0n1Km4Bx49pF2Lrb2JDKrqxJH3p1g%ThjEhfRszeb9EV;a6h#I?-0HJci#%{KHgUSlv10b~% zW&W}RisDU35=$W!UVBn{Q1YQ?L@UHS2Ivf|J&+%4=FAbT#Vdb=>tRX%Ju@Ka-4}rj zUzg!y&JkyrF7-O2vg2`z_Mz)4VnXZ}`vzEhOl3?6!y5Raf!rg=o3QEK9Z^yuqNVVa zAv-2Ser(8vS*(W^{H|25tF>{0^44rA^=nE~SZNB>M3i}JN=sO2S+d5IwhaxP5COG@ z&w2k6iVQ;ka4r_ZZScF{ObH-PuHm*{aT~ug{1ic6hT9+HWyj>!CC`>jHC8)P>+52x zgN~3fCac)TfTDCwZVk(=e1&%mL+-<$KWf2*`C-9m{o%-iqb?F*VLb2-dfN@paTa=^ zH)^P#@8!Azok2Y0S#jPSVjBas@TqPtIzYv zi9i*+HLDfmWx#J|j|uV^{>kiH;QK&PfdP#7Z nI4NADO5;`R7eDI>Cl;*-Fn@pX4+*l#FMl&C6K=ff`_BIW=@f~( diff --git a/Utils/__pycache__/ControlUtils.cpython-312.pyc b/Utils/__pycache__/ControlUtils.cpython-312.pyc index 52201fe280dc6e2c84ffb9258ae3416bc867de46..63e2f0105daaa53da00c456ec71aeca00c68079c 100644 GIT binary patch delta 199 zcmbQ9wl7j%94!yyqHY?V8_hK-&uSZ zH%@k8Rp+=YZ2pOXjgx0~e>=e*#u~uOAY$=Rh(SZ=vUv2ulF1iYMVS_qY<|hQmyt1lay+LL^KD`A$&H-q z%nTwDn?JK>Gctx?2(RW`t;D!za=6Yc=4*l)lYi=*Wo8i4+8?A)nNwGn=@E_*%^OL-p1pmtN=9W1;dX9hD+Mf8wxK31YQq_ zxEK&|H6Ut*<9fHXZWpzqH!JZ9GqGH@@wu|ul3$ONv2${Uhz7?41@+Gi%A7KrCyQt> zGJ_cVMD3a7dST{$;b9Pxn87liXC}{VzRQB@Ko@XoP8O5QX9m)nJ0u@50$q?TT@3*D C=R{cm delta 181 zcmexr{>+^3G%qg~0}xCOW60ze*~pi~BBbwX6;n`Il98Vmk#0-}CJTrM_ b^Ia2EpR6jG&&(jCv3a`WBSxTARnpY}<4`rM diff --git a/Utils/__pycache__/JsonUtils.cpython-312.pyc b/Utils/__pycache__/JsonUtils.cpython-312.pyc index 218c0bffd57cd50ed71f9fc4e2b87fb0ef106e31..4783fe534e09ec89fc7a79c2da30977339e38d2f 100644 GIT binary patch delta 1041 zcmY+CZ)g)|9LJyM?(*llT$*c|rY2pJ%fDXDsOaiiYt0&~*a&ux?jNlFsZB>xqMes? zE)Y@Ji>U}EK7+)m7{*?tD%yEt1op-a1{-~0YpyiYyl~77!8g}9OqN%Bp2$G&#pidw z@9*#NyT(W7J~l3KTnoU@XLmq!LHN$NL|J-v4^2+bg=Z%BClismW0TQ$CW2A6 zFQNnNkY3>jR6ub7^fl|?u&oiNN+AEt&M>fyMB~6I#j;%mH18`OuQ=S-l9!SN{&Ffk zRbH&u2 zWixEflZT6%;?$~Xq(+m-BeS(;CT+;LM$OpN`6nP|gp95%$ z!CZUAWXtt`+pd^=h5p-v3xkVe#UmwuaLvDG#lJ`Khkgy;i~JT@{_w!^p{dgNk+t#2 z%6LQ>pRLk>w^W-=$n9eA-xp3?i^s^p4Le(zX$Fn7w95u}o^E?ehQD-+tyAbHUk8QF z@4mYBA&G88Ydzii68EMGtgL|QJ)>RNHXYRn>|JSH+5UQ3U+!{Nfrj6R_KP;AG;vr# z$3&4P!XEO-b!W-}t}q%#s2fL5jdgV8CeThmX#M=VLPg=`;$x;cA_u Lp-=^c&7}VTznC{x delta 884 zcmY+CUr1AN6vuzRd#~HB?cTfVcAeYG-Tl)|`-7V{OR-(7XhcLvktC?3V33+qk%j$% zUMhm_`aNt>)PjN)1=Zy zCsvQMZyBZ>;De5VQz_>(`2Pv7IcSzCkLn9=W=ieRYA1T*|jrN8nega=VgRlPII_@{dFdc*bb zm9X5GkW%~7j-0*~Yp{Vi#y`XOmB}iGfOpYVYa6$y16KP_lH|)jSgK_|%31B&gis&> z%_?iwFC_ek7OMBuxbPE{zJbz3kf~^!W*$;0n8I_!n`6xh3AH30IiqWuLGAX}@Cl+t ziMV4&M??qtjG0B_qL&0#A4-b5h(`c@5(lAx!Vaf63a^h_tJyRUxVHVxT|H9_Af(Mh|0L1j{VqMT?Z@ED860D$9B1 zjl$*dq6Ro?X1UNkQ^NGvI<@zOJLlVTgG!oaxKKtHT6xcfMmOPlMUV{CE1HrFO3C${ z6n?0}9*lpxH}%eR^q^O@l>{~)YD=00HNlGJywzlu383HJb^)-B875F*fo-#_owsd~ zb)PnisLN-ivK>9M=&TRl#uf*9;A^eMsWVKVw7ebg<~S2$68OW2tHm7~5{Rrj3P(_9 zU0lV|=5{C-wp@N8>ZoT9lPz$sw&Z=Nl=rKO( bS_g;w#!vURMb4wnZBA~KgpMVkay{uUYvmas diff --git a/Utils/__pycache__/LogManager.cpython-312.pyc b/Utils/__pycache__/LogManager.cpython-312.pyc index 19d0b7d52e114a6d3e39a214d96e2778f831921b..c0a64244f849799f4f6515a888872d66e48b2c34 100644 GIT binary patch delta 269 zcmZ2fbi9cBG%qg~0}!b1W8BEy$}FnkY88{5pOP9AP+5|ZpBIzqAMBX9c@cAw0EY@A z149GD2e!$M@}iRsMcUaX%70>D;*^`bOGKL)NN;{2!p_L}Yx4)u^Xx$CfNUirW5;AG z`R8DT%wUD8lRXr~n1Srgi3+DU7)2-RXew}AmUQ~Wz|Ltj*>_YlMiTGFazn$zcu|h83iYM8BdV8A{g+2frZ!fLP*#R zNyYhcGv!t=UzXJQ#K0_QI{A&U0SA!&&J0vAS=Hn|2apD<-pp-!jG6J*u$}FVnY86vZS(1^T7vt$493SuKxp^sbkpQFoWC@YQ zOjG118z`u9ipx&o{lLl~q`3Km2s~* z3#>AAILb_ Z)8sxoh;czMV6%bgF=obFlfPR8006XPMS}nU diff --git a/Utils/__pycache__/Requester.cpython-312.pyc b/Utils/__pycache__/Requester.cpython-312.pyc index 40c35df16941014743b4ba2e05bc0d46619858ac..4af69638468c17be8ecb08741a2fd0a62a86a937 100644 GIT binary patch delta 95 zcmaE3@Wp`lG%qg~0}z-de$U9>$oq&{RNd7oCOJPPH7204BqKjBCeuIIF>^8_iwonN y$!;t`vNuE}KQS=!s!w3NBOr5KK>4D8@&d=J0%{+bfr6Viuv7>${+g^Q;S2!A6dscR delta 87 zcmexj@Wz1mG%qg~0}xCOW61o!k@pd^kcz8SOhIKyMt)w5r+;vKyrbu278V!A>61NK rf+TK>N={(BBOr5KK>4D8@&d=J0%{+b8N{SE?_jAAX1q1oP{J7i&bAxq diff --git a/Utils/__pycache__/ThreadManager.cpython-312.pyc b/Utils/__pycache__/ThreadManager.cpython-312.pyc index b4aa5195be68c2175ba761e6e546a400dce894e5..d2834ff77d5c5c85c9699fca34b1fec960f618e8 100644 GIT binary patch literal 15186 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 Nx-^rykU>NI{{dD=!j%93 delta 4269 zcmZWs3s9WZ6~6y#cmI9Avdi+=U0~VG@=Aa}FeV}bNe~iijfs#rxZ(eakcB1xF2*qt;*AWBL>-67#vY(?O=4ZDiZIMvg)P5}2T( zUD>E?S2e2I`9@xru|ajarcpy7QQJZZoTw8NqP~R{l+SXF1{!Tbg6df$@F!_vAE8F$ zf!L)&E0bWWTbm;ZZd+GpP)xA(t>MV&OzM6*Jq47e2Vz%`9G-sjs ze&^fMXU^bLOh2W<>)CdS$3JCV=9M+;c6A*bNYV&Cyr=kkI6hebYM2(by8tF$-D{5p!R^&vsg?7MN zQ6W(4p{J3kY%}73A}xm()dDSYEwsQq%btWe(i$}&H4?eNf@TW-)LckZyN9-?OpAFv7 zFHyA}F!;?0-L@wsF(7OSbOiQ@68?qCLysfel)GLn)pgSXMeN{8egJ>SSF5xz@GGPY z>{6$AR4|aNM8YJP=3rQI0-s<)yW7GHaBjC-a^NS_r3w#>!j1TtI<F%ZZsvf@HzhD+C%66%K{trHQ5$Z_wP3I9-*h*P z-!eGe5zD-;pg0LJKw33=JO`m3MK|4M#X-A6W1p2H1pWj!T9ose6guK#k*t@QXW{5> zCSu2T4e7YTpyCA8D{MC&)!T4pibunDQ^$~?mUX|8QtHxVqX>ykxp|OO(xZ%M=ErU6 zIN-9{x|PS!aa!Q!QwGvZCus3MQyyaVTO~KHaO5%xYA5b+tSTgqxlRt?+RHDI_Iib% zP0+1^d`{9zm`JF@k$@CwinO+i_*2L3UPFRw3Wl2ZN?w?iG6=~eB#V#*-zLmW^655( zBcV=NUlbCYY%0M?fsQ?5f@>9s7g%B$rh=l_DXkzz^g#S7iMU^mjm6DKepDyNN>E5p zO%loVFnJ`zjNBv=bqOXQ2+x?7#b+Ui@`!dlcR~*hE>H7}+Mp|kCO3hAcQCjA!`7h;9Iz5vaSyNfw*Ze2^Kg_$q za~|^mGpLTKht`Z4-M6*)tF%16NVfXL?P9zsea-f@Fp4&OnJlJt<#%RE0E`~`| z)usWAO2s>z4oZjjIZKo(9(8kV2K=xg-2(s)zeV*v)W`JEeX2gbk7`i>#;AIf`29Sm zqFX6wPw8-)%jeQVyc*{p5Ao{lQ7qYxndC(_FYia)t@!$W8sKp~ii3(HyYZkavuN+r zOat0a`3+nBw6q$Gb{T+NIeT{c`4d;p9G!k+X!@0rC9zV7KX>`kWaydG=Rtq^S3kRU z7GFL7s+5N-+(ia*I3$~IIU(i0r@+1LCW^x+vNG^j?k8llsKLc$5$aR#tp?v|nuk0Ls-D!1qYMo=iqwJGtw42yyJKYQtPIE@%|%o ztH^Gk4pK)>gFB$d$6cxTWRb?bXVyxxLMj06bt<7GsT}HxNF>#|ocK*oW~oFR&}9Si zxO@7Yx33;Q^x11aneLBG|LCpHe*WWi?k=($nEKW$SKs)-)xLr0=U?qA#&eotTpgM~)Yk^C!ZK2kVMaYo|FfJm9R78d{K4k;vS0(7kY8f(S zk%ipL(!GSZ2_dl|qs<(k98w-44X&bpK?X?TXT{MWU>!xVw&Ol0nNCv=(5EzSP`ojw|J(E zsS`%;n9)088d)>q9o5Hu74O+D88=@c6TUH{ZzOZ1dZa8~)Er+a#9M;n#`eo5>!1TW zCXE&XH-q|^ekd}$ZQPjKJHsGz+JNJW+ZL3Pc@t6c7Mv5-yfJIuNMMv5c`9C7_d(?c zCGmW+&k5^eW7fyMOfp3A&Wu@Jd?!o*_uK|ejR{@I%h<>;7ft*|#9T~Gueq1GcrOS1 z?-*{QR=LejClqig>=7dgg%A+i6`>B2lK5Xfcj4lx%eTV9g)r+k;)w(k79(?h!W&lF zdrLs*S1-YP*(_M`E z0r>|6 z+W5R12*~Z#_%*+ix(EN>zosE)4%bWJLD*GoXU!I^nh`~t7F9zcNKB!T57!v%}) zYFvI9CInSCD}%*Cbxt7}EZBqWkxc*=(Btd**^37A_M48 z0Zu^Y-xj!fW%O1rsWLW_xk^H^2w6;dInW4}NOHDwOTuY+5QRypMk$O8!IT$Y4wgtH<3W58R)Pe&^bYBeP#b>)bLvL++N9 z=2EM1ptNLjEvQN?kc2`K!(G9-TwOTegd)({DRv0+DO)eAwgg&(P~xe?r^V*3NJx^L zc&gOBLN3urp2{aK6M$4xnLq0&2*cNbz_FSveTpfaYozj$u5fhaN451I+P3_`f}6@x z3?EqfHpOjQrc%Ab`~RA{3LhyeQ=zogGf=m!!hb9i6wt?Yg}7{O8)e5Q)_Q38%!sdb z^==~D;=Wl=N>u_%2~Be_5DvGC@VO_<7O$ISl^XeDfZZJm1tkTMFs&UCX`MXR5)1_* zl6)d0P9&0*QX`Ppk>n%1975@YBg75=uoW=oImApl-8fdB)yr}MHC%NQk^UBg6s9Ci zSSTnJrWG$JZt2^nTB_pe%&Oq@kbkG`W^Bm?VO3q*U(Sdq9-?`1YOgT}|1Of|QdIFxM5z3L{{b?A BEiV87 diff --git a/script/__pycache__/ScriptManager.cpython-312.pyc b/script/__pycache__/ScriptManager.cpython-312.pyc index 03baef60e6a986f8707a5557a6d12620e29b26e4..4ace46f9228498f233e0732cf4ca95afa891786d 100644 GIT binary patch delta 1598 zcmZuxYfw{16yCjiZ(cx1ATcH(M1mp-?*J;P5FxQ*TNQi-kx@~s5FIc9YVGjq%V$R^ z+YXF&#Eu=UrCM-NZ0oC!L0hR}Ku8_6IMYsTbu^B3$36eI;BneGE&CQv=po=D_UKUv$3MMthAtH-h#{$vIzwm;0VE! z4l?jQNsr#l2=|Tm1A#gLcbcq-M^TJZ)MHHZ12)2fD`OHFHUb}r`9stbk>m$RZN|;9 z;S5rf)3M$VIDnh14}`n*bVY8$7JE3NNRG|;2~vHA9|WpXaZ}dEh)0{vAnMWaL6q^h z*hZxkx7rSidekvb?_X*^stH{}^BJxKSCyKkR9rx>6BLERm(M~JN%8XcB`_C1D#;b@ z&ZA2-P_8opQ6z6VrSxo_O%3kI#R?MYLZ$qlgTw5*59;HW~mosbcYZ zqKK|GR4Dm!;zz~OF$AE>Q1ns@Gd6RFRT-QGd@jh~T!wQsp?J*+i}p$FoTs!a9&1-V z)=KfviHUIa=xfBB%s`zR0X}?0kGG$EMO;z@ss%NI4G`CyO2f8O7T88=P6Y(PWU}pi z3?$!mi5b{IdM)uHp^op>2^{O(Wyt)U%qon;B3*l+<`Sld2|C$4!Jd;XjBLs(R zuiV0VrLrx^D_78t5r<*nKm_73X23A+wLn_Epa@noRZJ1HRs%pi=WcE|6hYn^7?j|W zDFJxdFA8iO6yx$+g|HW2zBL=J!}{B`^kJKCw<9=$7y70NLIm_oD{k;vL_P!~XTv&x zJ-#}+x?nI5s>r8&n1QKs{;cv`4-MA4Xt-=cly|TviGk&Oj!5QHGQ$g-HROum$bS%D%&cLFFQBC zXKK#nxLgljG4jD=uQ9bxm%BIfxV$?f$CH+`tI(s%^%|e+(=DP-*SSECbs_bUxCeLP zxT`WnqvTylQ__1uUe!dO1juy5_ZH#F4+~*7-uf`Lwvq+Gih9>O&f)T-G>>ie_&fl< zwPYqx=rq{{i;dv>#4I75bxqYf^r9ceSt-Au%9K;M7%g^8WG|Xo-cAwlp6OzTQP3@5 zc`M1(P}ilX0N$Fz9r3^;%i>kNm4f8FK4N%U&T>Re^?05;{zP& zs6P$yd^+TLI?ug8oM%Q}u|6*jdE>Y|GkN{dObAQK*?)FGVGYO4f`eI&1rSaXtYA3Z epZ1Pj5YD9^pz${b=F3;AVK(%GQE=1p0RIA=b`9bH delta 1602 zcmZuxSx}Q#6u$TW^Cu=?Ad5m0l3)-dEJZ-JKxMD+Pyx5#QUU`ijoLtfPMyMlK3J5F zGM-i#M;Vi=_|A9F`R+OA&Rrhw z67=s9NUzPED?xOu--+Ho7VeO4V&%q6(}uF`m8G^1He~0lv{+KJ$!^xufJexIz;*^d zB`Lw5FwAjZxEYenB0~sxhg#VIV*>09UCf{maEAWI4lIZSN0^R52I2}ElH==eBKZ~5 zqKCo^9gEaPawFv)rmCgjOlzY_lEIR0;0Cmj%@}TP(FjCH7X>HMPq724(3v%1WJktX zwR9D2cDfGTo~Le(Ij{k;Ez3AG-(Xk6C5w`@TWTeE85pg3OiMoOutu;*7et6vN^5CN zGmb>^YD-;^boIXz?cJZn(nMr<|8@mqQp2Rs03RN=afM=3=>bKVS`f7savpYVwE!N} zD@k2z6D#8Enrd&W5oEjkVDT9rxY9b86r4OPz|D~QvmP`Z%dykVc6WGzX^oDl%!P)h zeqa-eIbF5ZItL>6Du8rk;njQ&0d<){7OQ{=$5x#0W~Utnyw1&jaoGPWYdCXvMpi>U znjKU@`x8ABIZYn#@n_5rYk@mgPM0V7-s!~u6UZX>zFILWKytbc&T>LdR~E_nX0{qG zo=d`o@aH)_E(X8zDqO->WewI%a5PE*mh&b}@tb}Ssy39ody;uGv-?#Wtfezr>x@^L>OW&f*BZ8rTAn5K7C#(AHoTx~Gd^8i)aAP=-p1{;_IW8p|N0hkM zCP7!c(!6bn=+4q~O<4?jpibA6N1%tX=^DEcJ(j0yDgw~BL~M&^#}#T@G&>&6@jNlQ zEQ+0o#5~4Cmxr;Fp_s>rXnPR*JP`9}jIQu!r}UV|fapp!`$B~&+V{~7j)*EY7Td`5 zXbZ+p(sW0F@da||?h>rN%pp{fL?!eAdC429>IFkm?|ODrriAf(3s@wd2a5X!pIlFb znotR@VXB!DW{Vo3M$W!R43QoFWb6G&34AhU!587tST;68a}m=HvO~(&Q4XQn@8>`tT3C@)5`yEN`lJzXj?M=u%Sk&g{(&h~t z6Z^EQUB*NjDZlF3o4o2^QI9dvMPgr#V~-)9g%c9wt(|_c5)xk*;dkKh>zLXK0rFBd zmVauUt~g8gu_xm#b13@Glo~