From a1640ac2d2861fe60f061e05ecb4f84cc86b7cfa Mon Sep 17 00:00:00 2001 From: Whisperity Date: Sat, 24 Aug 2024 19:56:53 +0200 Subject: [PATCH] refactor(server): `massStoreRun()` as a background task Separate the previously blocking execution of `massStoreRun()`, which was done in the context of the "API handler process", into a "foreground" and a "background" part, exploiting the previously implemented background task library support. The foreground part remains executed in the context of the API handler process, and deals with receiving and unpacking the to-be-stored data, saving configuration and checking constraints that are cheap to check. The foreground part can report issues synchronously to the client. Everything else that was part of the previous `massStoreRun()` pipeline, as implemented by the `mass_store_run.MassStoreRun` class becomes a background task, such as the parsing of the uploaded reports and the storing of data to the database. This background task, implemented using the new library, executes in a separate background worker process, and can not communicate directly with the user. Errors are logged to the `comments` fields. The `massStoreRun()` API handler will continue to work as previously, and block while waiting for the background task to terminate. In case of an error, it synchronously reports a `RequestFailed` exception, passing the `comments` field (into which the background process had written the exception details) to the client. Due to the inability for most of the exceptions previously caused in `MassStoreRun` to "escape" as `RequestFailed`s, some parts of the API had been deprecated and removed. Namely, `ErrorCode.SOURCE_FILE` and `ErrorCode.REPORT_FORMAT` are no longer sent over the API. This does not break existing behaviour and does not cause an incompatibility with clients: in cases where the request exceptions were raised earlier, now a different type of exception is raised, but the error message still precisely explains the problem as it did previously. --- codechecker_common/util.py | 21 +- web/api/codechecker_api_shared.thrift | 37 +- .../dist/codechecker-api-6.59.0.tgz | Bin 69705 -> 69677 bytes .../dist/codechecker_api.tar.gz | Bin 62389 -> 62374 bytes .../dist/codechecker_api_shared.tar.gz | Bin 3723 -> 3682 bytes web/client/codechecker_client/cmd/store.py | 37 +- web/client/codechecker_client/thrift_call.py | 6 +- .../codechecker_server/api/mass_store_run.py | 1379 ++++++++++------- .../codechecker_server/api/report_server.py | 137 +- web/server/codechecker_server/product.py | 236 +++ web/server/codechecker_server/server.py | 241 +-- .../task_executors/abstract_task.py | 9 +- .../codechecker_server/task_executors/main.py | 5 +- .../task_executors/task_manager.py | 151 +- web/server/vue-cli/package-lock.json | 4 +- 15 files changed, 1315 insertions(+), 948 deletions(-) create mode 100644 web/server/codechecker_server/product.py diff --git a/codechecker_common/util.py b/codechecker_common/util.py index a9e966393d..5827a99b10 100644 --- a/codechecker_common/util.py +++ b/codechecker_common/util.py @@ -13,8 +13,9 @@ import itertools import json import os +import pathlib import random -from typing import TextIO +from typing import TextIO, Union import portalocker @@ -55,7 +56,10 @@ def chunks(iterator, n): yield itertools.chain([first], rest_of_chunk) -def load_json(path: str, default=None, lock=False, display_warning=True): +def load_json(path: Union[str, pathlib.Path], + default=None, + lock=False, + display_warning=True): """ Load the contents of the given file as a JSON and return it's value, or default if the file can't be loaded. @@ -133,3 +137,16 @@ def generate_random_token(num_bytes: int = 32) -> str: for _ in range(0, -(num_bytes // -64))]) idx = random.randrange(0, len(hash_value) - num_bytes + 1) return hash_value[idx:(idx + num_bytes)] + + +def format_size(num: float, suffix: str = 'B') -> str: + """ + Pretty print storage units. + Source: http://stackoverflow.com/questions/1094841/ + reusable-library-to-get-human-readable-version-of-file-size + """ + for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi', 'Ri']: + if abs(num) < 1024.0: + return f"{num:3.1f} {unit}{suffix}" + num /= 1024.0 + return f"{num:.1f} Qi{suffix}" diff --git a/web/api/codechecker_api_shared.thrift b/web/api/codechecker_api_shared.thrift index 3e01ea5fbd..4a41889bbc 100644 --- a/web/api/codechecker_api_shared.thrift +++ b/web/api/codechecker_api_shared.thrift @@ -14,14 +14,35 @@ enum Ternary { } enum ErrorCode { - DATABASE, - IOERROR, - GENERAL, - AUTH_DENIED, // Authentication denied. We do not allow access to the service. - UNAUTHORIZED, // Authorization denied. User does not have right to perform an action. - API_MISMATCH, // The client attempted to query an API version that is not supported by the server. - SOURCE_FILE, // The client sent a source code which contains errors (e.g., source code comment errors). - REPORT_FORMAT, // The client sent a report with wrong format (e.g., report annotation has bad type in a .plist). + // Any other sort of error encountered during RPC execution. + GENERAL = 2, + + // Executing the request triggered a database-level fault, constraint violation. + DATABASE = 0, + + // The request is malformed or an internal I/O operation failed. + IOERROR = 1, + + // Authentication denied. We do not allow access to the service. + AUTH_DENIED = 3, + + // User does not have the necessary rights to perform an action. + UNAUTHORIZED = 4, + + // The client attempted to query an API version that is not supported by the + // server. + API_MISMATCH = 5, + + // REMOVED IN API v6.59 (CodeChecker v6.25.0)! + // Previously sent by report_server.thrif/codeCheckerDBAccess::massStoreRun() + // when the client uploaded a source file which contained errors, such as + // review status source-code-comment errors. + /* SOURCE_FILE = 6, */ // Never reuse the value of the enum constant! + + // REMOVED IN API v6.59 (CodeChecker v6.25.0)! + // Previously sent by report_server.thrif/codeCheckerDBAccess::massStoreRun() + // when the client uploaded a report with annotations that had invalid types. + /* REPORT_FORMAT = 7, */ // Never reuse the value of the enum constant! } exception RequestFailed { diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz index 270aaf28fce6581ebdd7167ab89aa50f240f88ca..3bfe9dd373b3c10123ea738654a506f1842f341f 100644 GIT binary patch delta 64111 zcmV)IK)k=npaiX;1b-ik2mk;800092?7eGq6I+rfI-mI!8Y1psN4t$wZvi`Qc!TNf zGhk?Jx=&w%AcR#mYGlcyl3=@g_J6?+Nf5pXRds%%)A?2eIMP# zPv4xqJUzcS{f{>O>-YPC=k=i9`+X-2y`c9Wt{?OVgCOXmc7Odr-|hS8fB$0x|E-ec zXo05Ez#`FqJ@LO=@?Wp$zmvPs)wj6!W^xtJlK4?h%fH2oWHO)ioa6pc@BfTutI^`V z=k)uY5$M};dH3w;)1NqfQ?^S#~nN5~xddcx4 zHlN3AO^=-e@-NUCn1>3e~%wFM6vaKI$#vyTyFGy23vn zv9562@g!L;CSOU=y6g2^|`3=yEZi z9`#0xn1BCcN+x}TSA&0D&Bh4qYJPh=pRt};!`{!y@*C}k4tU&qJztP{+^rUObA&T; zSH*$zGkVN=c}$j)^bRJ66m46 zXohGfK0G{bvby@lCU(^O`7I{PN2gE59g)7(oqzc`!Pta)IhdfCkmE_dP3~|{*OP0s zn|JZz3U__r_y3aosTDIX(Mv0_yJF5SJM^Q3Ump3 zv-z?&o!n0F=;$1i`StQAMz4enhz@0pcAwup(h=*1HaOz<|9Wz>T9EJPSf;T;KHh!( zCqjkn)JL;>`Wrfq)s)&5&QeZ2ZMnt!2L^9zEoVwT{RBaTqymnr+_b#K(8+e5lL z(ih13NG%gxo4X0VP;)Xlw#*xZ31|)IXKleM^+GHCLFspb`=NLDHXct#z2*I#y6nHt z7vD2P;V1f$%$U$2jA`-;OlEu;;sVgEW6Qc7jS(UFF`7(AU#I;3D|G1yQ&Wt9SAQdh zN+UrRICVqYfEo+(Mt2G|o{()FEti-JK zi_yrxqsug%|2&kt@G4$RexP0a5%=(hB#+Y=fX9IMfUTbOME8JC{Oc&ehc_cvag4{n z*pIM>(keU@IWBw`etw%=eN%`5I)AJs;sNMpF5({(a+Da0(blkg(u>g!&lmh(s0((? z>TP13VJ;UZ2vf-JkI-QADY+B~Y;rT1p;5ADo~6f}PF-u)|EQPVKDK)po7vfs?itru z#J8h~xS#ReXhARq?+aPRZM=x5_vjwXzLSmnim(Oa$!v5RAM!(=AVRdb9)DdCCUK2O}O_?jYDjW=^KZt^yf|(D95|!B3g6yIgLe3M!Z? zli@MGe~r;*UZbrr3oqrkeShzP=x;LKI5w zlVyCHs3aWm+*OQO$Q9v{tOY%O%=xIaB4jOf(~lG~qG5ru+j!rQjDLHzN(h%HBi<5v z$;8ON6N)EUQ~W#M1bwX>AJI~hyUEpRzDm&Ly&WySW74;f(h(QA;$(6&BeWl(B0eLs zu^=X5V)ghO?f0ms-qqvB*(;oquf?+Y4J;IS)D6V+@isMR?;D!T*BIdw5*cGc$CA-6joFR3Rp5Lv$ zB3QJI2vhH-BZTSV7c^(eYZLMdlPpxlO_lQnF(%S-W<-FW6XL>7$W>10{RorY9XOr? z1Qy>d@fAZ#$C3+4Xo3krbgjzqA%Pur_HFbdCUPF15YeRP*VkCmLu??PA_7eR zFQO^)#gd+kpnn8RRAzD^AvAntcnjzu@li*2cT=oz&S&V<$u48+#in*O9Zk@F)3(au z&@Pfr)ZG=7bB6A3l8hD;azC#Zi2iV$CZ2Har(XSoaqsLJpTE$C5=5&oQh$p>Sx#=~6h=2A{F#tBrn(N<9 zkvW1$Sg1k&I~Pn@*PJOAH_);psSS}Gk+^jm$Ml%5=ToE>Q5MgM*Rw}LwK}9LT_IGv z!Slt_qbdhF*9lr8rdq1>D-=ZhUuK~r!XeVi#D8#VG@e z0VP5l;VUNj;}JveBTN^^F&3SU6nTQ+=u+M{wl->o0OpgXsk-n+$tUlFOl~|UGA&{= zcz>CAJL#2MEG6gSLVS9m`lgK!G1U_1&vZY0)Q`{KU7o!>eT;73?@O}Z_};NGu+*)L zquwrs1^_oDv$N#9l&-k;IYMVR8WS~BB3%q_E2c^#Z0c3IVN{Kf2D%WkgrhS1s&oVI zFu1P-*-#(Tc$8p`N;TCp5P4gXXoLoR#($?d;&YUHEq6%6sw8&~&sA!!A(?uwbsJP~ zaxLi$=9xE=1!nu7FOIUiJmS)xV(4P(0kBKgshdJ12&4qk!$KV{#(&0(yccIP!zMbU zGh@jj9w9yS@*7peF#XDIvvS@qgA0bmqR)`-Y@i?Z=Abby$GhqW!@q!vj(Jl(5=mu$Z1oQ9N6(KrV{Cj+* z(0D?5#fB{6o6&;$meSe>_Xh-s7;+IL!Guu}rN?tZJC{`IR@_2(=b2ZH3NYN4F}lUZ zGa=Jp(^tIsferiYpJ-AHi+^cjj<$TtN7D4fG}T4?KdT9IAz_xDphLk-ot!CR{qtMw zrN*;C+l(ZSE3`s(7DDg9=Gu&rmft6SaEu=SOn_}wc-(t6Nr?J{UANc0zatS8ZOpy6 z24b#X@2L_?v~R3tNlHM@kti_I7Iq|$j9vdk&h-FK78?%IYO2~A8-Fpi<2}UIEkyMn zpIr3LE*|%OJ-Il$;Jf?xv&-M!eZ1`b{p7=klk>~7(~I7_52|HcANh1s`&WA0i~gNe*8SNVD9H;{+iN#zQQmr>$|U z4`EXujekstuk@O|;y_n`Z32Jez`AEwV0hd+xx#D#Zv&^jc<2+!td(2w_itE!*Dq|^ zX_)JFDZ;GVp(p;qA0kaHa#c6U)DKx(E^um+9 zxfXjfj<1oYiXAc?F zXMFe@naoe=RqFLFO9C9R2Nk+efeY7jbZ-_&8((tc#*tL$uP56U zxPSzGF%PCAD6CuY(&F2a+#{0yyKspT-JVVWQ3?b&O|Ykmwr1v1PRL1Hia7|+u-Hnq zFCuH+aemC5tY2gKc!l^%`AiZF_!v!&7=N%a8Gek}h3*4o&tUL05$a8ZXP)gG_rMA( zjkI-)7FXY}@0uZlbSr+kNB@29eIgS_6HPf>KhyRMO~#7uq$ALg`aHCEfE$SSmxs?u zA6zlP)POQEW=ZGb{A9)yQbHdEQVB^%H2}=N62sX@v)*xJ9xXY_7d=5`&!Yc~CVx#q z%82SF8H7DSd!(5xwLRk##7`QO21Z$YO-Y=~;B@OQ;#ivmM|L9NAlyN#o87Ds;vwk( zF^F0Etsgg@NZ~!nE)C(=qd))o&%HNq{`R(aO3szPIa81Z;|QMyYW?R&k4}gYD&_## zj>ldmNkNGa3i<*L&+VS_8|AqlW`CwcZQxSX1%DNY|9^b@H6GKqU&P+3`a*^?nEnECOyNpi}f7|;%im5e$uIwX{_gL+zw>|L@2}pSo&N=Q^BD7ZOf|2TkDot!^kanS2^sYl&R!3m z&|jV)2?2k<}6gXd>x2+R%KEJGi1PJcW?#|$0bF@ItL%Q?mlh70+PjoZ{-u4I{AeERz6L}{KI7YTpSF8_=Vlc<4e4(i_3TKvwE`C-7&hDCyJEyZToyZ>mMv9LJDOL+~Wf8s?Av{)s zVin_&*eg(;{!QDcNdE-t^vNNoXL-lZuRSIWMePI~;eY3)&PTaQPvAbD9Aysvs5fA{ zt9C_>H)k>c2yhsD;W@NUEV_tr7A-gn4=&cpU2L|KQVY#|y;#og=;tiHfGCNn*9i?b zNXQ1iK}uSd8ADA>oEV8LizLl>Zx-{_T~dbGQgE1e$!`F=;^VTt$2JW-Q>E9V-X{$A z`4HrwY=4UI$jV1xXt6qQZ2A=#oUqEw)@Jx*P7BK#^8Ek9#x z)B%~s#3xfAf4pE|&wGFR6Z-d|X#Q{I)ewe?rylWbpxjaM&XN7DVbo-HqfTAjm*cxt z^6h{?DJ}j;lk*jN0nL+c78ieaKWFb3bNtr#{(!en)^n2mwc|DophaVX+8famXqH!!p#(Z@(n!P0fs%3u!Y1)H9oE6_7 zX_gHwTY~bkoD4jM{v8-cU5yTr5|Pu}mXVOn4Xl9NoE3@VMF)*WDk|-Uttbbj>&X)7 z4pX9pP7x%N$`X-69)&?_7<>p^HP)IXF=FBd>Qpp&0an9^BVaekb1tai=yLu&p1m3^ zM_HZ&v@r3UC2o`EIqH8KoYbf3C|Tm?f=MaE4(%Q074jRvC-}5vOOPL_6NN`fw*bb{ zSDS_c$5nO#9yZkg7**TI6ikX2rjIkj3(XBIFRaB2skoeBIUFm^ituLmp<#MDBDXt9 zx9%i0yl-ue8|K_O7qA}ZPIs8%+&b+P3=kl7m+!075U;W8xA;%aL$~)wl(#ksUmUDxBnL!AXG|3G^bBbt2{X zkn&tex)AR?%RHXk- z2UOiYs^iBA(>?KCid{cFwGR%KcK-B~8H+ahp6k0l6uUY;?a5F1JIpprI)U82_Wchnt%%VPq}`HFGXVvYZW&?$ z%#+a>A^|s(^chMLFMNbg^oP&-jW>94QP1$r|HG3|8dnPNh~LoDVPaT%$&;}fSqkzp z`yQh_HXc4NlN1}w0S}Ym8xwyCymbDLz;Q#H|HJ10sGt9%M7|Ct%w5Kd8O{Ox3!&{# zetmcO+mqh2ULR-Jdh+h|Yy6J^{l_`}+j%5mPZx{%;w7%j#i0LkdVc!h+s23e0+cU;p4@rG8Xy)!C#!cJOA>xv(vxJd9o_HUrt`VJtNB; z1p4nUU!qecA42T|(hon7)eR!;ci#PAsLb)zuNTYFa+UBIzWZ;vFJxZlf01*2eRB5Z z^wpQkcVAw~sjwjKt>* zkEVVF#*zzGTZ7}sO)#uW`wp#3*+np7UNkTT%`>RU>|2Z^u;f5H-lHt8Ab72bqE|5t zd7^!PVs39uSanQTXRed6U#aLVHvqNl$P;RLB_8_u^!oV<)GKFm7WCk~dAPg<8@4f- zXj?rSlTJBx8&iLl#|gWOfjt(PTLf6j%7W+y{H_2K6n(&IHl#G;6bk_PZPD}@P|c_5 z47%C^viw+{0nEw^&wzu0k?3x{NOXA(&B5rQFkgw*2CJ3q4RGy-hJbYOghN0RziwC@ zksfv}Al+JR+Oz+>r1l>@GufI+VDs!hu800#CjYDNg#&+U|FQO;uI7JLAfcqm7~<*E z-%j7WKmE{q{qg)IO~#H(Hc)p5+M^3ONAS*lA=5c|f5!6Wf5F-#WIjL?v05jY7Y%j6 z_f%gr$ggWluC^&j#9g8>tR&?Suym(8VS}w?zV|79T3QJoFNceZey6jl*wq!A1UjGtXVWtsb#`stzFh+C*!E=-313y*?eCV}G z5R`qcLf|y7#dz}1VjF8gF%0yzZSpR z2!>s(BT$GnBA{6v zfwla&$yy~Ec6GF1iH424QVXOW?l;XEGh?NgB0!!5Uop|PBCaCgY8Dic9aLkJ0Nv9T z0};(Ki=l8e$R*>B}K^8pW`sBz8vyBL|Ubg9)>=-g|#Px8A!I zWZQi2fwY;MczibdnBePK^PN+slWK1Ua=@)DySZ0RdB@O#8#y!F#@;w-BU^EH=fqj~ zh}WP=R-m=kSu518%ThM+HVGKe+GzJwv{h=K6g^Q&uI6`fsenDw(7;f|H;A3G5oF6M z>@2;MYP`%0Z5dgU?|=9ix66OVwmc3j2N_%72kFt$%s{)ck`-icc_AafAT{6M5ZEqK z&3y=U&aSsUakY9vyP?gYF%f_0RSazo!9@IF=}i2gOvN9+Q=qD+aG8WM5$rNqPgK16aQb}x*ImMEc7N&AZ zVx?&)a9p+n@Q}(!U^KaTP%YorRX%ECt$eLu~P7ZdZR)Q3u^}9bB({lj13Q$sdR%l`8r}0c%FVC_PkLP>N%TwLomH zXc-j7U`KXUJ2Ja}9k(EJ1h($thAlMR_9GiHU~O5-+KO7X6-6dnQRGy#6-5KcRunm9 zZAAp01HrbUNNr)X6-6$$6`{Ylttg_lqR7p+6-A!GRup-4Y(;;O_Yk%s>%#AXCeQQ_b8?0TC4_Uh+zrfms`fJqMWuGq8sh%)jOp+I&Msv#u3=$U**= zYCfb#ObH19)e?VP$3*gqc#edtTp?S6?x(@&0T`$?+f+$p$~KSeX2yoIJ;_W#99WN+ zTdU3H6PjkQ(Iz$B`J|>r*KPxHgiUO^kIMIGJeH=X`g%9cM0ZY!wD$WX`O9Lyy4ylE zU98BQ2UUDFWI<&cqO}Ld;=2S7rG5oQl3T2{?!4(Ci+Fz(B$Yg1E28X=!9lrY5B)?3 z%w8hzW}DedJZrBhCu!x9jiyDc>|ncTrO9wYEiZ1Cg_g;Ri=o!YiHpIjEh5ch#Tr1? zT(krR1SWrRbCbU?$nCI`d0S>D({H3Tk?mX}4CsJsOO@92WwW|jLsot9s&yL{G@Q-p zwr>WkMSOod{}EdombGDR-i8I@UIl@p0NzR*){}jGVEk`y#m9{ z1Gc=@MnmnYO(GZXn`V&N!E6lFKF2`m_+S;`ol}2=p#!eXl&uPERbZ%Y)& zR{7n-%pjNZ?{Q8&lcIQ51-x1yEC^_;^KGs=AAA5U#6cpS`^BPvhC(oi~>_$#bLi{Ud17lS8>=comX)vvL+7uU|vNsS<%9n zS8;zh;CU6%UoSW)&8s*Z5M-Qv(yh#&m{(kyJQ#fQ+S5D_96U*t&~$H~=V za<|l7iAHHpZ4L`o=@x)orH`40DlJuMORRq&QW@Z^Jk?~?5B-d7;mE4U*fa;!#!0b1 z=*kL$YP|79^i%+A>eUiADx+A@hA^*_-vB1ZN02Qmh8)z=q0FF`u{Ehi@zb>mR1JSj zZ0D`MQH|2Csi2y`%`PIB1f-_YL|{8dCauP_qXBC*^`bmr%R%fLJ*(+Fs(X9d${WclUBSN)nC1(od){{Xh`5%&%HnW(i>Flo)FEG z_h)~L7c5pv401J^PQQ+>zAN&Um`;sZ87$`gOB+Dziv z_)BW*_(IsZurAR~-%b+3`?XPXmdp3B)Odg76MXk&ODaRG^OR9?IT<$7jl z#al#5M>jEN5BfNgqOoR#u)REa^M*fIpqoH?#ZzEr63*aZPLmph=vZ`bh?JV4iokSJ zv`r;I$JbN*zUc6I#eH7HS3iI9_L=bVqg&keXhGI3cV1p16-{WX&%OWsU+uIAT8`ss zjM#U!HNTg)De+1@fYbl`^7Q@X*}HS~n*PrAowJ~mySph{>*?=T@!e9v_}{ZqlN43b zTP!x2ukzq`!H}rj&2+wMr%u86DH7o(H7V1S*bWg6j+wQ9maLU;OnQIjP*c7Z(THeM z)mYCltG_}5=+MC~5=)oM)8)+OwN9B0Bi5qLxkuk9brH(yW5sVuScV^ zC*L@g=GvQqK;z~|>9%|&>BiX$(Yq!VgJ+0hexJ>M&U#XltxV3Z@PWi*-12R_{5Bt3 znqHBnXLoQTgxxf3ZR&rXj|q*F`0Q})(fRCHT_g7Sn{O%p`V=3)f$o!i04HoyJkma*KR!q3)+)6Wf30A7!Udi=$M-mqvMv zI&@|Ja>sMr(QTE}eWv_{ux@0~U%S_g0ChEy|-l>1BqCqQU|Mh&47FZbdSIR#e z^{yw=WxTK~p&CnIo13$vn`IXz=hFZysf1Q7vKcmyCb0YMbv6YYx#d<(ax0Dpd#L~ch}PiaF4SVaKqPmJ%P?yKmn`a(B3+gfmWtp|w$-$k z8;a~GJClEPB0j1iQ5%sU$_&4*`hX| zHaQ|+(=)j`NDi)-=lQw86K+aH^Nj?i}kfo~Gqn>+vn9Z2CSiEgi9%^8wN-k@i)5 zB@cfe-Ly@-hgV0c>!HgYanl|+Iznr&-<3M%-Ff$(-BnBC*Y=2MHEZ|G5ylXzA=1t( zHmL}zlozzRM-iFl8+ec{%GesgH;!40^cSE&qfCI0wXP1K%JD0y*k$0?odSX5%RjbfHMb zenPF-Uud{TIs8fVt7f00zh$pQPp$k!kV2~ircoiSq|>J-tj{k`dVlIIXg##gsXc!; zH$28WOgchGaZIn!QBR*!hS)2$w1C24#n++qZOi*ehWvQ`-{E&HVINtPy#Bu{xppxT(2f7<&`@DPg7u66Lnrn5*?+8 z7r#d78c4^}cy_b=MkK91N8i65UkiWyG(&!!pi#6YhlfnoImU}Q5F3&fwN(21W&ZV_ zbqk>Un~|?)tgkYf6g>~p2Jc6L4^ZKtN)2thmQp~W2E(%Ce*D20w?7M6cPuEr7T z(4aLK?)8C&hFLvHzq-#A?TO&yFT6k?k{&VQA-v>2KM|_jlSBNu8T4JBjF5T%XUTd0 zzvSm;_rAv5ro?3zz9`OWFPAc-?>6dzr)Q9*DzbOO|bf$JOLg#v8CfKDExZ=`5#O7k|6X ztMbeJG{mrqs~)Y%wXy?LWL-c98|Gi;szCK`W42Gr$=0F-WjC~&>)^(m{z7-p0 zY-?SmPII@lae50_99a3NIp$_8`m{b<6J1wlXcBbutSnOwX|0~P=6QdeThMi`U-u4lnctgAM>W9fvo*6C;qz*@Dvx*a zdAtS&@b@D+F|G6&|AEy%Ye@ots!Y|pdumUX^RH9ot321Qz2}|f_{A&gGPlTsW)&2_U~-+8Q!|-f;y@KLxl!Js++kR_|`< zeOXSwUM;usync;+?<}`3v!*Wd`&6CE-R0*n@@|l$*T(+N^7OTEzvEVXQvtYUjQDw# z@7A;7t36jU;cJkawctY~;cA=D)Xpq#U$Zb@CG&mlo$s>szIcC89XH-<#YcDAb`K%& zW=;1sZ$OtV_iNyP$636oSYjg_K2wsgDF&}}tn&9kA%9l^%?5m0DzZIR4;1XHecP12 z>^rk{?s(xwcJh6hS~^wY?uKmA^|=&q12)T~>Rp>I^QijxyFDxlD#F+_r_zcOw!pAh z|0>^dtMDxjU%-F9*xy=GV)6^CP`MHAmMZYq#BF6=TBWv;l8cp8+ci(2%L;DUrFC4* z1uEl~rU<+2$IiV24<>k2zeHOif7H7|TM%JvD45t>h;gQBWRo$*O6L_Gy#c^x(L<~4 zlA<;(2Oi%tG$6=WB)t-3ck#`y8eF-K2>?R@ZRsZU}3=b&BuV( zyD(i30vMw@9ckRq3pEPt(0w(PG_dspX}Mwf4TV0!F!2by>#96uh=# zM%7EOK}lncThv)$W6e9%WqHF`1+@WT11JF7h@_$QTfy_8!*$E)0TxZIcLTMqtN^iA z+1m2bL;YRvtmsf;NnMs3^lIRH?bZNsdxJtj-OGQ`S&3kstI_dy5!q_F%{+&IO9QX> z3WD}oEhLb^Y&IBBua;ZeEl9huJiT_E;!3X;YV3BGA1mZ7r{ixFSZ(#4es`cF>SmuC zRL1l!zcQ$W=UryXP#NL|c(Pu1xe=DkyQ~nzCqT^lxLOIjB^I5oN?h+I@m?&0u3HVd zJb!<#wbz|x&-ttAGI!=PKS*EIUDl`5XuCmfT?6ks%dBf+efRzMZLsT13G4>F_Enj- z3T5Wov>bV>e)k5Lw$hR1+iTS3uIgytA?{tcpzgcZ6&_Bm?e2TYABut3!~Tx@#yiZ$ z|HJ;5^WWX*>O0!dr_;&Tr^=0a^(!rIaQuJIWc@Mv{eIwiJ?M95;Cchc?fr-A2mQey z2>Pg9e}H})p#R;(2CNX_S)i#jut@Y@PyFwe{P*pi>2 zy_fUbJ49#~y@USoU~t?&d_>#7de=LDciB6Ab#~eNc>d<};-dGDcOQEvA5ME0AJKpR zx%}IP z{pAU|&G`Ez|Gs48jQkJ%_E-7yUs-p#oJ1Yv_^GKWZiz={^8+9k@!fo}{F20r9|%?e zJbn7x>6`bbA9}App1;J@@1l2d{;Gd>arxon%S*IH7jlZJD~D~tA6BzhqvhxeQw)UE zjQ>rU9^v7O)$BEi_3u%}^_+j0PHrd5^pErF>m<(niqx3pTQm#hcg)1)Nb=vxc$ifR zme~8R|LOtXad(nKXD5t!7H8`G=hQHqnCr8ki-Tt%zX7B3U6C!p{@Fgk11NtVff2+O zD{a9s#ID1mXrF;`)I-y@@q&HOM^W}Mt=XEePO7$OjDJ^!A%8HZFI?0(8QZ6Azx48=~w}zt*&RI+nC%=Bn{xdl}~IX>SWnF(Yz%s=~g3c$!^a0jdG<5=!nl`#TM?D9a5x!M&plVZ2ab7WJh+P*=n!-S{cJpRU z!KzI=!^*|PVHu!k>KuQ{fGM7eDS{AGMwHCgi{<=|e$HMb@amS3ZiVsAB;PXoN?K%H zP#e(cVliI;V(Xg5BxnfSE!{T0M0v`fq|9e-5bu<)kaQ=O%})4ges}+7k}S2pk9waZ zFTnfx85Wndr5NP)bQ4H)&+dr!TQw1r**YrEy$tKqbR}aXPChl-Zro;YxA;6#a{`4pE+o1qcJq#E+p2oLczpv05{-5$ilg%)? z9vlKKA(*l1 zk`ids{78x@5F0C$P$*DJK*2=ksMiOi4@m(afkwL+)I-hs5SJVEvGDo>bP8H)oi?Ty zi^D_S{lvcfyLhp@KVY4RN8k!lvF?A-djG_JR%jpl1@?bp{Gp&ydiBZ$E-6)l99wE3 zMwVZwh$xVybP@H2c23HCIbY3|b&RN8w15_fRdTHrq$&u=3RSypyX!wIhVCk(1o5?L zJ}C#;8N1JB2(8uUi}ZQ5^;RhB>cLj#D^^`^L|unG780debXiz6@FBQc>w2I%%mXNb z#LV8$GqZnl{mp7b%`$58)64no?PxYGp%wjxG}HOw{%oA#*Q=TKW1hAoJ25IudJ|Y@ zQsC2!5XVd*QHyNTsXM}#dxJc-rvg{xeHwo~Q`b>r0r1S7f|-|`^5*!PRYrG9 z83hiv7K61)X(LK0l6}w4|58dXMSC}y-B{IhH`sQ&>M1bP1E{DJxnV7pVY5|KTV&)X zv(fZEnIvbk>v@r)I#SxM6AWSGw!i=|FElg+I3he}=^Z-Bd^b&H$08*11b_@ulx6=p zfR=yxH|guFf!1k$n(e}dFmxe=p&Noo-c4>)49N_X5<+rFz2M|Ue4~h;XW`3mb>ITo zD1pvK32aV^vgVLx-rB31x(yHR0VJp;H(9euyHD@EzGHfN@AZR6X`>(^rjyV zn)6g7$$cwg6dvhUBs*fr&%2w{>RL1G<}%4tB&3q$5WK7=(X91)z-$8UX7FQUC^_NdXvyTmhiqpV+UT;dG_= z{_!5$Q30%(d{fDEj3OlPcC%(Yo(5ee$xenWc2@baK7hvZ?XWG~qO?0Y64cOitOuZF z55OQY>N<{7)dS!RKxM~qN-I002LWbKb|S5fS=n)1q3obPg|ZVdWyf&~lpTM^Gbuat z3na!Ga_~4_MIV6USs%de2VWY=Tw5~#&f5PSzXAKd@DJW-Qy7 zMMifZL`L-I3jxkVMt4vkGP-|`Nn~`LI`(nb>85?$8haZO{Wg+ZDl^tDnSEV%ZL_cI zn&dv$h0MOLTV(ci-L@pZZWtq-tu#j3jA1*PF$_50y0W9SINJPS)`V44j@gtQ%VrbX zQ>)Ia$@TRIa_q@(qvTsns}8B2hvDPQw=o*vk`h?GVja3zui|A)hPi)Oj+Uz=3%nfN z=)-l?NTjac#Jy8ZK<=$=BRn0YP5z|B#={fRJ_D0c&P{6%7s3M%(;ze%+d?9g0ZU*P zFVr}HlNf@*GgUqUQxmue&V_yj2G0|`pvzcR?8=9=LRZ5kJJ&Ot?4pV$J2!+)cBMjRBY_fAbtxb-}ZYY0EcA~@z0_P^Xq7o~fvBZkkuVb?F`rS0ynUTXA7?H#CGh(eq z^bIKDc_4~-erXi(l;ih&7)88+*2at?ULa5e{rN(GGZgWHA`}UYC=%voodUN_#g*_O zaHYg6W7o;(M+%2k?u6@X`kiqO9-H5?_?fpX22O22Xdiyr;+KDa+2Sk6>Kk6Rc#+n| zjL^P(+2YHWEgpZ_;upPa@r^HAe9wHo`J1Ppwf`WGSNt_l$M?;sGfbh5kfcnw62Z7K zERQQH9t>ez@qMk08CQb7z!mi83jxk>CFmF6N-!|t3i(pRAL(u>$Po-Sj2zuCl@tiP zjT%azv3AMj6F7fsyLB_0I9AvkQgoyoDs8WH z9Yb6MV(;2;70V3g+D4@1wbFpsn4v5U2 z5t%*l5)tZ+VYO$XR_MU_0PUc3$_gcr05V+_ojQd9kJ+!mSePi1cdqgkDQ#>xNY)d0 za>h5Lfp|ko!P!yyg^a~-_NX>$ptsKUHN$^$?f4z?!ovq&*BorU@GZyv1m-uKVZUnJ zPXIsS49kB%;tZ5WoT1?nXAo#@%o=YfA905A5of?3afU^YI78zj&d@1m?#ssWgpQ5p z+408^g*2>H9eYeBlES(yC

z>h^yQQ@Hfc*jN!S5miWRV&n}ZMiwV$5R=r;kennB9`;PI?=myl1^VSJLfG7BgZy+9 z=_W??8VoB}zGR1lC&f_}o{#z!n2LhuauzS=w|9seN?DS%g2z|x1U!XQ17H@~wMxO1 z4xCiUNri|>7|hC%4ZqqC&9C;us;~A#_|<=YSpL<1sJz+_4X^e?PivFoqmi%nL-}ey z(zeLw~45CBoVdwK1Jq0hbSHBpzuM%jf&tPhp2pzL!{i6$PnZZ z4YW4q8x_eQhe!rFL_EkLDhhIlj6n|ci)3}2;RAUNqcA_uA=^~-;)UIOD{j<^ONoCW zhtJJmZE5;J-8B`;GwIbZfVLJA7B1x1ckkY0mZ1qm5`TW3&!-1F7j|!uz?zP?SBKf9 z7vxd2_6zbTGP#ta2zo&tMMV$&qR8fo-|}4X8SPpc;6Sy!&;rMx)_m%$XH(@&IPZQa z<K8+b>I1=S*1P>prKZb^TpqFS_}ifVufBIQ)9l}}57G*};f-TJ5q3iXYt zX$yu}3268zS_A_!%s)`)&qag!QwJ;`V)iDpNzno%TxtzikT~jvzzm7lkQ%g87GUe> zZK$w8|F`AG)kAXuxuI9}arF?cAU7;uL2js2kQ*8*$PIt{S{t*O zVJIue4P^znA+I1eEUF+kG**zKUy6L@qH)E73d6uYz~23AbVd@_mSViK1r>&21JAC9 z#(HYQ5PWt$4AbKOFth~~I`rWBWHy^GNB9OM>m=VR`+%U!3G}Q>yUY{CG{RJSt7wL{ zLMtG&u9F39Xw07E_nm6aX1{+Arb|Nol$9)9V{|25u#Iio#zYfLFtKghxUqF(CzH&L zGqG*kwv&nNiJiQB@4X*=dev$C=+)h|YuBzi+SaHL6J+%gI)wQXr;p-oRBy%c+S+YY zce-6)w*1NO`=jy^y$8<;AbW!$%8@7v}uUIz+J}7Iy+MwLs*Jz=>Gtd{hr$*L(o4gy?o`m|`8AfvBpw?f<;z{V; zTYho2)Ui8hB%;qbOpv0>^^Gr1dDbbYY{+~VmXny4uJVMZ?hX|qn3I_O3r<#?@Z`?8 z&SbMd@yxsasgatGj7e8W#CYdynCvsOe+c@Lvhlx3y znkj)r63z#16+yH}PwH)NP4WXso!J0Gka(&)ZNGAHfLkSQ^?a@pm1#3opEovDAH zZ8suIze+|C%NKxS)pB(E@7>)#jS~9)_8+2w(rhL}oAi(mw7Qcu$Lu83!hDAmRBQU> z&4|A4ZnC?t)NaP*(cd_ESVfPA;(?MnxluD>2fPehvfFGt>bS(=KU%CV;}peB%UxX3 zO{5SJ*i7GTJ_6^^jr$n z1Q>C<1zEFIqBt%5{xDVbOoEJHO>Y&}!Jr>ef6EEPFME~u4W8+o6(cDgiGq9S=hjk@ zfeKkM6pwn=j=NvSl(AYZzbqT^1fMMA-qN1TXgcBizxYIUDd*&yFFTW2Y#pnR{TZEo z|174r@;oz;qO=5jo9`U^!IzWuD!$pfIh98*A3R*x?1k-A{Ornpc_Jyya!8*V?Cj$B8!<2mgDLI_R1R9T z^3ImS=%`wx_?e+U-AvBF7){`|Pgq{WombQ?;Dw6F)Ai#lgcLux_TP>*5N#{cclTAu zQt-GK49EX22J2YE%$&5)0o?6Pfclj#e?&r%41Dylf$Imv?+Klq@e1sp(>gmxW|dDQ zTd&}W4G_RLw)B@?uqV2l8<&XDengc7*R^Ztz>h%WGKAuEPlBhDuef~>!bb5-9IQxu zA&b{(RYnl<9SZ_q8FwTC*cKqU04*by>5mdsLLj8z4~Lg=H|zt(yP{ zma@GLzwwCk&FJ#XrVH|wz$~KNA0IzvU$yEau5ANMZ9h0RaSh}f>1KRgY0_R%)#INA ze7E#%^c5`tZm6X!L3d%c@@a8I{Cb2qh736cO&bdmj}7!K{HP?b=LC}t4-FJPI!ztq8@zyc zfRqu`A0L4(lS%W~)Nv#kJNOB{5P$^$`ccR4tvB_EMrG>ER3JcMsxhvs%+2vn6{(2L zeX)r4wn>+c-rx#Vu&F^bR*CI`f@=0aHOo=GIsdp>)Gwf=)!)(cqL`sU-tg9f8}#j8 zCI&LP@ucOdoTvp&tT8mp|H>TtecMiS!oSqGY1z))iIXmw#InViKukhxgn(QXcHt-j#AJko ze-sun#z2Phd8~?bey~}0z$KY2T|F2~st$CQqGPFuv)5d|hOS(gWkgcXM3EIf5rZE_ zmFWmeY*i|k-cbm??P`h9>dd44x*Xy5=rWdbg_G&tP%PGs_Sw*-4tLZy-HhKcjSlCT zetXOG6q!$M>gMx9hZk60ND79M+EW%mbxeTr@4&~(dq~3TYGsD%Uv-ORr(ssPi!lsV z5+>_S^7Jy<9&S~}>bbGyA{grxI1TMON>kXjk(@dkmRGLX+*X~Vt8xsz6H|Im3hpxC zqoRK9+I!c$Rr)QWs_1sAx;1oM{Hm-z5Gz_51wN2|Tp>a~O9Z(;^~Rl_>pHcvwjAri zXMlRC-{f$MICh%JA4TC(N)gAuwxpi68Kt6cu3u)s1Y3!wF2GxT7kb=q#bQ&(B{MkkMXAViDZZUq+F8r;e*oUc+L@M2r93d9T4ieDm`c05Gkd!~Wr>ifYjC@)8YW?|xG{ zO!?*M|D2nV%?*`3Is-W$_OG7kW!n-w%_Uu6RoK4@*FP-Vs+#|Pc!ouR_P4?(xCLe; z#HKmcnjHlnV702pmb)MUAYFtG+?AXPSe{^KM@k?08l3B6kVJXLM_5Osf6{q}WhAbr zt7+f>Jf8ldRsR>Xk49+U28bd(uoJFf`cfbQ>M%%6(E7g{J6(KW^aq+OL$kr20J!G zelzqBSrIfWp2HyuW+4WWh`l3x`8;`me$Xr-N&}V)OGqP<-*_GVlFtx@Qb(pF6E&uy zQn0Fp^NmI7#o0Ow36|mDp%_HJ7GJ`GvJ4xqa*7C24?Kc79)#-iDlBw4*Twe?v|E@~ z+1Ua78O)VBMDssJ+_J=VJ{ZxBYUlBVeV$!z;&`@6H5%}?NhLsL_XewI);J4hG#?Em zA(NhP^~k!KWzY$`ff*B;6q9eXgw*HHuO2<@+U#F#H>5k5(tIFLEhYqd1^ZfQSNInT z`d&;7L0qRw{}`2Pd4uV4{KL$^&0Quytwxo17Rd;!!|jPvYFH2#1a zbUb0As<|e+E_)yx_PFKwRCrUKB=c)zV7q3EzO{ma@H)boyNzVAW_v-JmD@vZrCVzw zlm1&c#poIgSa|A9^vVD>5|k^#8+paK(b@P{JNm7 z9}I|7KhW;<>FyBR)VPkoE`$RDf509mniHC8sn6gVt3OTSLi~Z*&)K?hL4kVjiFXO` zV~g%vb%g}NQE_j80ex98kjMhUrC5ys>8K1)&HB5rBv;_)>EG{#x(!aj{k11XVS@Zy z3>Fa22I7^#JhG!aj0KBT;BpMP5=43kT*_t3SdPk9>*~atL5C|c>}h%)fLf%}J{b!i zW6{66ybb2@E&A?^;eT!B=S63%Z8cVMo4<<>chCS$>m-6DVqoGOAiTBaTh<3ob5N&o zxho>df`0~Z$sk?6^9Q&4na)GB8xAzzl3u?|+cnA3kL$Y8Pia;`j^9^^ENO>UP$6&+ zyWYSwjA>Dy+NswPs|?7-EpE0BD7V;&8pbB)1K@h(GlAv?CX3lk{QyeSmW!TJ0((BZ zfSo4DV%L@np=4e$qcF#HDO*#NX5xq!k0(WR`|;DQ+%1GY0Bjch-(>#;)apcCcroA_ zN=H;e6XdkS+T|0YB}b^Oq~7Xk%Ua-;klsZP_uwLmFKdQiA>Dob z`^{-?-H!l`nj~3Icmbtx6ls@O6M#XMJy( z&x=fYCU8T=RTHbjR{ZFxVLNxBx(6sVmTvU=XKmEDRZ`%TE5&aVvR`Yc)g^QX=}$cx z>U>86`1$!fQGck_oj@G#kbRQ5tf~N;PrMe6d=_}pXvySkT}I3_Lq+?&uZG}qS;taY z=|gRWx*DU)zWdh5Y5q!a?Q4Ec)#XsWxt@xoxwC|Zw7BLo;MqujRle%MY6+;e=yi-n z_NZn7i<)W!7E{8^3auJsYvIv~wLSq^?_1BttS(+0uA!QXr;hyW{zHxvcPqoxbJbXd zh($JFu1rr|UM)afLmb+SvkNx;)aK@nPo|>t= z$|e4-GP&Lb-_zFW$8$iktrqjJ&5vKFfB!UjW-dBEu>FjC|K828;A}PiTPM!SZpOLc zDn2=eeIB3e$|nQ-z7uWsL8GU32e+emzbHrGPVigbuu(LD|I#R|(LK&?X1~}z>R#3{R?e-;@$2m3 zg3492ta!(Hy%)tX6N7y-y0-Rr3TF0qLs8APA&U6eUOlfIi`%YxQs7gdj2v zX>ehXjm9##3rIv$4SXAvqG=7Q;o)(-4O#}lYKcQ`b^ie=Y6arCyQWk%fXd}vNB_VQ z`^eXBq~8&+opcI-jV=?>}!NIXE5anJczfw$X|`W zG;!C9H^`ts?e7@t`xY*TjyJci&V1TMz(0nDwDrTTcN0^yMV2Q;oF>j3p$H zIb=@g5av$XXlzle=}!FA-DY9wXDx}PQHzT03efIFLB>Wb!0tgSa!@&XmwfduHHE#Nfd_J>Qsf)YRxnSyJlfTnMPs6b{UrPa~W2GN)iRTT4BV?7k?s# zKl(LMA%lMXbxc`3YD6oEqD(!BqE4OOU56`pmt-KR0|t>zEt@Q9vv- zl!QX}I2YKSm)B@s^;l)loS6FH63Vgtyz=$iXA;bjz_#K0om^hJb)IaZbr}{O#$*SJ~&zx~)cQR^c{Ws80#_PUXI+&9ZJS=v>74LA_6_3D% z7b%#iRsXI+$#+SYldvWh+^Q5|&E|n{F6}V-b$m897OExzj0+=YADLdCu?lUIfeBf6 zC0rtw`c$(*5BRL|@|%t&@|=EEu-Wz@GMPi}q$(`q6!Hk)#NhvoA_^ zApx;y$2K8gV)&H~m1#WS4b8r)&3SUI=-5lM0+sC*8}Wf94sy{%$S>HUFnK>yaq->M z4BitB*q~s_vR%jOVzu9iB05I?7f}IAvx9>=MrL~want;i@7TOF>(L=g+!`amp^PYD z+&l9;@lBT7aO%rvA!onfn>U!7iHa4>H!Ohwe+6;W97DT1AzD4j|5Dcar-$elU0lk0D%zZfiI@EID8y^Vjbiuw|%yhcoW&bmgM|@4-Mn1{ODt0@445t2pAW z7NGIq{za^l7LTLP!1`h(9rjo3RF)jf-X+$nT(ECTB(T5rJGSq0IxkHzhPJWNYojD; zBE0K-P1qUGpK;b1FBD{-e83iqSxSIGB+Sh4H zp`nU&zM*}zC5AWzp+W$w1qGk%F{Rlix7>H85RBG$n7(W!7Ox$}1U|6-Y&5M%5xSP! zAQ3t%FhZz%w7FX={;t$&98ESZze&q$JMH};6O%|9PD+Aq;Mf~a9H@M7ALUVbh*|t4 zbWr*l9*uIzc5!OqCc#ji5ecaPlvoE zxjj_nH72Gv*&L^h%i=2BVz{?$!B-rRSEblvXTgkYnwP!#%)M>cY=>syJ!06w90e+=BmTKxbb|s@V zi1(~eLFZ;bnBYW}6G29hlQ}OqHK@W|5gZEiW-bh~8POEsa0C*yki)*9l8zs^{wh_V zAbJ8M`bHy?;^^hq7g$h$Bb+_&F4LQ=d}$*_$c8=qgbK>GdP-4a2`qT51PE2 zA2bxy!(wJ5Z0PrmTo&R2zrQBZOeuDM&;axDh%gh_hQ+WFRTt)Ui>=OQgZF>I0dQ_9 z)cJmiau+LGN{lWI^1}w}3rBW{nNQrlkXIwMrNfktU6|b9vvphT!sS?eI74Z(U3kmj z$44%c6X&ts^Fo)U)&3FI)D_YhM?(Zl0_8!olJ!G$1VOVN+aS(h

0obrd-ivW=z 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

|gM`A?ORKvXK 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 z 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 z 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 T{DTdDKL7rI9=m8^0Dutyo}UV6 delta 38682 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 diff --git a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz index 1cbe7905540aee7e7029009c6856659f4e593f70..6a8bc206aaf1bdbb2e9e00de83e1c186864e6da6 100644 GIT binary patch delta 3680 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 delta 3721 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 diff --git a/web/client/codechecker_client/cmd/store.py b/web/client/codechecker_client/cmd/store.py index 58e7f307a9..6889b6bdd1 100644 --- a/web/client/codechecker_client/cmd/store.py +++ b/web/client/codechecker_client/cmd/store.py @@ -32,7 +32,6 @@ from typing import Dict, Iterable, List, Set, Tuple from codechecker_api.codeCheckerDBAccess_v6.ttypes import StoreLimitKind -from codechecker_api_shared.ttypes import RequestFailed, ErrorCode from codechecker_report_converter import twodim from codechecker_report_converter.report import Report, report_file, \ @@ -58,7 +57,7 @@ def assemble_blame_info(_, __) -> int: from codechecker_common.compatibility.multiprocessing import Pool from codechecker_common.source_code_comment_handler import \ SourceCodeCommentHandler -from codechecker_common.util import load_json +from codechecker_common.util import format_size, load_json from codechecker_web.shared import webserver_context, host_check from codechecker_web.shared.env import get_default_workspace @@ -66,7 +65,7 @@ def assemble_blame_info(_, __) -> int: LOG = logger.get_logger('system') -MAX_UPLOAD_SIZE = 1 * 1024 * 1024 * 1024 # 1GiB +MAX_UPLOAD_SIZE = 1024 ** 3 # 1024^3 = 1 GiB. AnalyzerResultFileReports = Dict[str, List[Report]] @@ -87,7 +86,7 @@ def assemble_blame_info(_, __) -> int: """Contains information about the report file after parsing. -store_it: True if every information is availabe and the +store_it: True if every information is available and the report can be stored main_report_positions: list of ReportLineInfo containing the main report positions @@ -135,19 +134,6 @@ def _write_summary(self, out=sys.stdout): out.write("\n----=================----\n") -def sizeof_fmt(num, suffix='B'): - """ - Pretty print storage units. - Source: https://stackoverflow.com/questions/1094841/ - reusable-library-to-get-human-readable-version-of-file-size - """ - for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: - if abs(num) < 1024.0: - return f"{num:3.1f}{unit}{suffix}" - num /= 1024.0 - return f"{num:.1f}Yi{suffix}" - - def get_file_content_hash(file_path): """ Return the file content hash for a file. @@ -649,7 +635,7 @@ def assemble_zip(inputs, compressed_zip_size = os.stat(zip_file).st_size LOG.info("Compressing report zip file done (%s / %s).", - sizeof_fmt(zip_size), sizeof_fmt(compressed_zip_size)) + format_size(zip_size), format_size(compressed_zip_size)) # We are responsible for deleting these. shutil.rmtree(temp_dir) @@ -698,7 +684,7 @@ def get_analysis_statistics(inputs, limits): if os.stat(compilation_db).st_size > compilation_db_size: LOG.debug("Compilation database is too big (max: %s).", - sizeof_fmt(compilation_db_size)) + format_size(compilation_db_size)) else: LOG.debug("Copying file '%s' to analyzer statistics " "ZIP...", compilation_db) @@ -721,7 +707,7 @@ def get_analysis_statistics(inputs, limits): if failed_files_size > failure_zip_limit: LOG.debug("We reached the limit of maximum uploadable " "failure zip size (max: %s).", - sizeof_fmt(failure_zip_limit)) + format_size(failure_zip_limit)) break else: LOG.debug("Copying failure zip file '%s' to analyzer " @@ -932,7 +918,7 @@ def main(args): zip_size = os.stat(zip_file).st_size if zip_size > MAX_UPLOAD_SIZE: LOG.error("The result list to upload is too big (max: %s): %s.", - sizeof_fmt(MAX_UPLOAD_SIZE), sizeof_fmt(zip_size)) + format_size(MAX_UPLOAD_SIZE), format_size(zip_size)) sys.exit(1) b64zip = "" @@ -1005,15 +991,6 @@ def main(args): storing_analysis_statistics(client, args.input, args.name) LOG.info("Storage finished successfully.") - except RequestFailed as reqfail: - if reqfail.errorCode == ErrorCode.SOURCE_FILE: - header = ['File', 'Line', 'Checker name'] - table = twodim.to_str( - 'table', header, [c.split('|') for c in reqfail.extraInfo]) - LOG.warning("Setting the review statuses for some reports failed " - "because of non valid source code comments: " - "%s\n %s", reqfail.message, table) - sys.exit(1) except Exception as ex: import traceback traceback.print_exc() diff --git a/web/client/codechecker_client/thrift_call.py b/web/client/codechecker_client/thrift_call.py index 32e5b3dc18..d41f7d7187 100644 --- a/web/client/codechecker_client/thrift_call.py +++ b/web/client/codechecker_client/thrift_call.py @@ -81,7 +81,11 @@ def wrapper(self, *args, **kwargs): LOG.error( 'Client/server API mismatch\n %s', str(reqfailure.message)) else: - LOG.error('API call error: %s\n%s', func_name, str(reqfailure)) + LOG.error("Error during API call: %s", func_name) + LOG.debug("%s", str(reqfailure)) + LOG.error("%s", str(reqfailure.message)) + if reqfailure.extraInfo: + LOG.error("%s", '\n'.join(reqfailure.extraInfo)) sys.exit(1) except TApplicationException as ex: LOG.error("Internal server error: %s", str(ex.message)) diff --git a/web/server/codechecker_server/api/mass_store_run.py b/web/server/codechecker_server/api/mass_store_run.py index 87ab4e2a52..cc95d88f5f 100644 --- a/web/server/codechecker_server/api/mass_store_run.py +++ b/web/server/codechecker_server/api/mass_store_run.py @@ -5,31 +5,36 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # # ------------------------------------------------------------------------- +""" +Implementation of the ``massStoreRunAsynchronous()`` API function that store +run data to a product's report database. +Called via `report_server`, but factored out here for readability. +""" import base64 +from collections import defaultdict +from datetime import datetime, timedelta +from hashlib import sha256 import json import os +from pathlib import Path import sqlalchemy import tempfile import time +from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast import zipfile import zlib -from collections import defaultdict -from datetime import datetime, timedelta -from hashlib import sha256 -from tempfile import TemporaryDirectory -from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast - -import codechecker_api_shared +from codechecker_api_shared.ttypes import DBStatus, ErrorCode, RequestFailed from codechecker_api.codeCheckerDBAccess_v6 import ttypes from codechecker_common import skiplist_handler from codechecker_common.logger import get_logger from codechecker_common.review_status_handler import ReviewStatusHandler, \ SourceReviewStatus -from codechecker_common.util import load_json, path_for_fake_root +from codechecker_common.util import format_size, load_json, path_for_fake_root +from codechecker_report_converter import twodim from codechecker_report_converter.util import trim_path_prefixes from codechecker_report_converter.report import \ FakeChecker, Report, UnknownChecker, report_file @@ -45,10 +50,12 @@ ExtendedReportData, \ File, FileContent, \ Report as DBReport, ReportAnnotations, ReviewStatus as ReviewStatusRule, \ - Run, RunLock, RunHistory + Run, RunLock as DBRunLock, RunHistory from ..metadata import checker_is_unavailable, MetadataInfoParser - -from .report_server import ThriftRequestHandler +from ..product import Product as ServerProduct +from ..session_manager import SessionManager +from ..task_executors.abstract_task import AbstractTask +from ..task_executors.task_manager import TaskManager from .thrift_enum_helper import report_extended_data_type_str @@ -56,32 +63,37 @@ STORE_TIME_LOG = get_logger('store_time') -class LogTask: +class StepLog: + """ + Simple context manager that logs an arbitrary step's comment and time + taken annotated with a run name. + """ + def __init__(self, run_name: str, message: str): - self.__run_name = run_name - self.__msg = message - self.__start_time = time.time() + self._run_name = run_name + self._msg = message + self._start_time = time.time() - def __enter__(self, *args): - LOG.info("[%s] %s...", self.__run_name, self.__msg) + def __enter__(self, *_args): + LOG.info("[%s] %s...", self._run_name, self._msg) - def __exit__(self, *args): - LOG.info("[%s] %s. Done. (Duration: %s sec)", self.__run_name, - self.__msg, round(time.time() - self.__start_time, 2)) + def __exit__(self, *_args): + LOG.info("[%s] %s. Done. (Duration: %.2f sec)", + self._run_name, self._msg, time.time() - self._start_time) -class RunLocking: +class RunLock: def __init__(self, session: DBSession, run_name: str): self.__session = session self.__run_name = run_name self.__run_lock = None - def __enter__(self, *args): + def __enter__(self, *_args): # Load the lock record for "FOR UPDATE" so that the transaction that # handles the run's store operations has a lock on the database row # itself. - self.__run_lock = self.__session.query(RunLock) \ - .filter(RunLock.name == self.__run_name) \ + self.__run_lock = self.__session.query(DBRunLock) \ + .filter(DBRunLock.name == self.__run_name) \ .with_for_update(nowait=True) \ .one() @@ -98,29 +110,151 @@ def __enter__(self, *args): self.__run_name, self.__run_lock.locked_at) return self - def __exit__(self, *args): + def __exit__(self, *_args): self.__run_lock = None self.__session = None + def store_run_lock_in_db(self, associated_user: str): + """ + Stores a `DBRunLock` record for the given run name into the database. + """ + try: + # If the run can be stored, we need to lock it first. If there is + # already a lock in the database for the given run name which is + # expired and multiple processes are trying to get this entry from + # the database for update we may get the following exception: + # could not obtain lock on row in relation "run_locks" + # This is the reason why we have to wrap this query to a try/except + # block. + run_lock: Optional[DBRunLock] = self.__session.query(DBRunLock) \ + .filter(DBRunLock.name == self.__run_name) \ + .with_for_update(nowait=True) \ + .one_or_none() + except (sqlalchemy.exc.OperationalError, + sqlalchemy.exc.ProgrammingError) as ex: + LOG.error("Failed to get run lock for '%s': %s", + self.__run_name, ex) + raise RequestFailed( + ErrorCode.DATABASE, + "Someone is already storing to the same run. Please wait " + "while the other storage is finished and try it again.") \ + from ex -def unzip(b64zip: str, output_dir: str) -> int: + if not run_lock: + # If there is no lock record for the given run name, the run + # is not locked -> create a new lock. + self.__session.add(DBRunLock(self.__run_name, associated_user)) + LOG.debug("Acquiring 'run_lock' for '%s' on run '%s' ...", + associated_user, self.__run_name) + elif run_lock.has_expired( + db_cleanup.RUN_LOCK_TIMEOUT_IN_DATABASE): + # There can be a lock in the database, which has already + # expired. In this case, we assume that the previous operation + # has failed, and thus, we can re-use the already present lock. + run_lock.touch() + run_lock.username = associated_user + LOG.debug("Reusing existing, stale 'run_lock' record on " + "run '%s' ...", + self.__run_name) + else: + # In case the lock exists and it has not expired, we must + # consider the run a locked one. + when = run_lock.when_expires( + db_cleanup.RUN_LOCK_TIMEOUT_IN_DATABASE) + username = run_lock.username or "another user" + LOG.info("Refusing to store into run '%s' as it is locked by " + "%s. Lock will expire at '%s'.", + self.__run_name, username, when) + raise RequestFailed( + ErrorCode.DATABASE, + f"The run named '{self.__run_name}' is being stored into by " + f"{username}. If the other store operation has failed, this " + f"lock will expire at '{when}'.") + + # At any rate, if the lock has been created or updated, commit it + # into the database. + try: + self.__session.commit() + except (sqlalchemy.exc.IntegrityError, + sqlalchemy.orm.exc.StaleDataError) as ex: + # The commit of this lock can fail. + # + # In case two store ops attempt to lock the same run name at the + # same time, committing the lock in the transaction that commits + # later will result in an IntegrityError due to the primary key + # constraint. + # + # In case two store ops attempt to lock the same run name with + # reuse and one of the operation hangs long enough before COMMIT + # so that the other operation commits and thus removes the lock + # record, StaleDataError is raised. In this case, also consider + # the run locked, as the data changed while the transaction was + # waiting, as another run wholly completed. + + LOG.info("Run '%s' got locked while current transaction " + "tried to acquire a lock. Considering run as locked.", + self.__run_name) + raise RequestFailed( + ErrorCode.DATABASE, + f"The run named '{self.__run_name}' is being stored into by " + "another user.") from ex + + LOG.debug("Successfully acquired 'run_lock' for '%s' on run '%s'.", + associated_user, self.__run_name) + + def drop_run_lock_from_db(self): + """Remove the run_lock row from the database for the current run.""" + # Using with_for_update() here so the database (in case it supports + # this operation) locks the lock record's row from any other access. + LOG.debug("Releasing 'run_lock' from run '%s' ...") + run_lock: Optional[DBRunLock] = self.__session.query(DBRunLock) \ + .filter(DBRunLock.name == self.__run_name) \ + .with_for_update(nowait=True).one() + if not run_lock: + raise KeyError( + f"No 'run_lock' in database for run '{self.__run_name}'") + locked_at = run_lock.locked_at + username = run_lock.username + + self.__session.delete(run_lock) + self.__session.commit() + + LOG.debug("Released 'run_lock' (originally acquired by '%s' on '%s') " + "from run '%s'.", + username, str(locked_at), self.__run_name) + + +def unzip(run_name: str, b64zip: str, output_dir: Path) -> int: """ - This function unzips the base64 encoded zip file. This zip is extracted - to a temporary directory and the ZIP is then deleted. The function returns - the size of the extracted decompressed zip file. + This function unzips a Base64 encoded and ZLib-compressed ZIP file. + This ZIP is extracted to a temporary directory and the ZIP is then deleted. + The function returns the size of the extracted decompressed ZIP file. """ - if len(b64zip) == 0: + if not b64zip: return 0 - with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: - LOG.debug("Unzipping mass storage ZIP '%s' to '%s'...", - zip_file.name, output_dir) - + with tempfile.NamedTemporaryFile( + suffix=".zip", dir=output_dir) as zip_file: + LOG.debug("Decompressing input massStoreRun() ZIP to '%s' ...", + zip_file.name) + start_time = time.time() zip_file.write(zlib.decompress(base64.b64decode(b64zip))) - with zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zipf: + zip_file.flush() + end_time = time.time() + + size = os.stat(zip_file.name).st_size + LOG.debug("Decompressed input massStoreRun() ZIP '%s' -> '%s' " + "(compression ratio: %.2f%%) in '%s'.", + format_size(len(b64zip)), format_size(size), + (size / len(b64zip)), + timedelta(seconds=end_time - start_time)) + + with StepLog(run_name, "Extract massStoreRun() ZIP contents"), \ + zipfile.ZipFile(zip_file, 'r', allowZip64=True) as zip_handle: + LOG.debug("Extracting massStoreRun() ZIP '%s' to '%s' ...", + zip_file.name, output_dir) try: - zipf.extractall(output_dir) - return os.stat(zip_file.name).st_size + zip_handle.extractall(output_dir) except Exception: LOG.error("Failed to extract received ZIP.") import traceback @@ -128,9 +262,11 @@ def unzip(b64zip: str, output_dir: str) -> int: raise return 0 + return size + def get_file_content(file_path: str) -> bytes: - """Return the file content for the given filepath. """ + """Return the file content for the given `file_path`.""" with open(file_path, 'rb') as f: return f.read() @@ -202,7 +338,7 @@ def add_file_record( def get_blame_file_data( - blame_file: str + blame_file: Path ) -> Tuple[Optional[str], Optional[str], Optional[str]]: """ Get blame information from the given file. @@ -214,7 +350,7 @@ def get_blame_file_data( remote_url = None tracking_branch = None - if os.path.isfile(blame_file): + if blame_file.is_file(): data = load_json(blame_file) if data: remote_url = data.get("remote_url") @@ -234,199 +370,303 @@ def checker_name_for_report(report: Report) -> Tuple[str, str]: report.checker_name or UnknownChecker[1]) -class MassStoreRun: - def __init__( - self, - report_server: ThriftRequestHandler, - name: str, - tag: Optional[str], - version: Optional[str], - b64zip: str, - force: bool, - trim_path_prefix_list: Optional[List[str]], - description: Optional[str] - ): - """ Initialize object. """ - self.__report_server = report_server +class MassStoreRunInputHandler: + """Prepares a `MassStoreRunTask` from an API input.""" + + # Note: The implementation of this class is executed in the "foreground", + # in the context of an API handler process! + # **DO NOT** put complex logic here that would take too much time to + # validate. + # Long-running actions of a storage process should be in + # MassStoreRunImplementation instead! + + def __init__(self, + session_manager: SessionManager, + config_db_sessionmaker, + product_db_sessionmaker, + task_manager: TaskManager, + package_context, + product_id: int, + run_name: str, + run_description: Optional[str], + store_tag: Optional[str], + client_version: str, + force_overwrite_of_run: bool, + path_prefixes_to_trim: Optional[List[str]], + zipfile_contents_base64: str, + user_name: str): + self._input_handling_start_time = time.time() + self._session_manager = session_manager + self._config_db = config_db_sessionmaker + self._product_db = product_db_sessionmaker + self._tm = task_manager + self._package_context = package_context + self._input_zip_blob = zipfile_contents_base64 + self.client_version = client_version + self.force_overwrite_of_run = force_overwrite_of_run + self.path_prefixes_to_trim = path_prefixes_to_trim + self.run_name = run_name + self.run_description = run_description + self.store_tag = store_tag + self.user_name = user_name + + with DBSession(self._config_db) as session: + product: Optional[Product] = session.query(Product) \ + .get(product_id) + if not product: + raise KeyError(f"No product with ID '{product_id}'") + + self._product = product + + def check_store_input_validity_at_face_value(self): + """ + Performs semantic checks of a ``massStoreRunAsynchronous()`` Thrift + call that can be done with trivial amounts of work (i.e., without + actually parsing the full input ZIP). + """ + self._check_run_limit() + self._store_run_lock() # Fails if the run can not be stored into. - self.__name = name - self.__tag = tag - self.__version = version - self.__b64zip = b64zip - self.__force = force - self.__trim_path_prefixes = trim_path_prefix_list - self.__description = description + def create_mass_store_task(self, + is_actually_asynchronous=False) \ + -> "MassStoreRunTask": + """ + Constructs the `MassStoreRunTask` for the handled and verified input. - self.__mips: Dict[str, MetadataInfoParser] = {} - self.__analysis_info: Dict[str, AnalysisInfo] = {} - self.__checker_row_cache: Dict[Tuple[str, str], Checker] = {} - self.__duration: int = 0 - self.__report_count: int = 0 - self.__report_limit: int = 0 - self.__wrong_src_code_comments: List[str] = [] - self.__already_added_report_hashes: Set[str] = set() - self.__new_report_hashes: Dict[str, Tuple] = {} - self.__all_report_checkers: Set[str] = set() - self.__added_reports: List[Tuple[DBReport, Report]] = [] - self.__reports_with_fake_checkers: Dict[ - # Either a DBReport *without* an ID, or the ID of a committed - # DBReport. - str, Tuple[Report, Union[DBReport, int]]] = {} + Calling this function results in observable changes outside the + process's memory, as it records the task into the database and + extracts things to the server's storage area. + """ + token = self._tm.allocate_task_record( + "report_server::massStoreRunAsynchronous()" + if is_actually_asynchronous + else "report_server::massStoreRun()", + ("Legacy s" if not is_actually_asynchronous else "S") + + f"tore of results to '{self._product.endpoint}' - " + f"'{self.run_name}'", + self.user_name, + self._product) + temp_dir = self._tm.create_task_data(token) + extract_dir = temp_dir / "store_zip" + os.makedirs(extract_dir, exist_ok=True) - self.__get_report_limit_for_product() + try: + with StepLog(self.run_name, + "Save massStoreRun() ZIP data to server storage"): + zip_size = unzip(self.run_name, + self._input_zip_blob, + extract_dir) + + if not zip_size: + raise RequestFailed(ErrorCode.GENERAL, + "The uploaded ZIP file is empty!") + except Exception: + LOG.error("Failed to extract massStoreRunAsynchronous() ZIP!") + import traceback + traceback.print_exc() + raise - @property - def __manager(self): - return self.__report_server._manager + self._input_handling_end_time = time.time() - @property - def __config_database(self): - return self.__report_server._config_database + try: + with open(temp_dir / "store_configuration.json", 'w', + encoding="utf-8") as cfg_f: + json.dump({ + "client_version": self.client_version, + "force_overwrite": self.force_overwrite_of_run, + "path_prefixes_to_trim": self.path_prefixes_to_trim, + "run_name": self.run_name, + "run_description": self.run_description, + "store_tag": self.store_tag, + "user_name": self.user_name, + }, cfg_f) + except Exception: + LOG.error("Failed to write massStoreRunAsynchronous() " + "configuration!") + import traceback + traceback.print_exc() + raise - @property - def __product(self): - return self.__report_server._product + task = MassStoreRunTask(token, temp_dir, + self._package_context, + self._product.id, + zip_size, + self._input_handling_end_time - + self._input_handling_start_time) - @property - def __context(self): - return self.__report_server._context + if not is_actually_asynchronous: + self._tm.add_comment( + task, + "WARNING!\nExecuting a legacy 'massStoreRun()' API call!", + "SYSTEM") - @property - def user_name(self): - return self.__report_server._get_username() + return task - def __check_run_limit(self): + def _check_run_limit(self): """ - Checks the maximum allowed of uploadable runs for the current product. + Checks the maximum allowed number of uploadable runs for the current + product. """ - max_run_count = self.__manager.get_max_run_count() - - with DBSession(self.__config_database) as session: - product = session.query(Product).get(self.__product.id) - if product.run_limit: - max_run_count = product.run_limit - - # Session that handles constraints on the run. - with DBSession(self.__report_server._Session) as session: - if not max_run_count: - return - - LOG.debug("Check the maximum number of allowed runs which is %d", - max_run_count) - - run = session.query(Run) \ - .filter(Run.name == self.__name) \ + run_limit: Optional[int] = self._session_manager.get_max_run_count() + if self._product.run_limit: + run_limit = self._product.run_limit + + if not run_limit: + # Allowing the user to upload an unlimited number of runs. + return + LOG.debug("Checking the maximum number of allowed runs in '%s', " + "which is %d.", + self._product.endpoint, run_limit) + + with DBSession(self._product_db) as session: + existing_run: Optional[Run] = session.query(Run) \ + .filter(Run.name == self.run_name) \ .one_or_none() - - # If max_run_count is not set in the config file, it will allow - # the user to upload unlimited runs. - run_count = session.query(Run.id).count() - # If we are not updating a run or the run count is reached the - # limit it will throw an exception. - if not run and run_count >= max_run_count: - remove_run_count = run_count - max_run_count + 1 - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.GENERAL, - f"You reached the maximum number of allowed runs " - f"({run_count}/{max_run_count})! Please remove at least " - f"{remove_run_count} run(s) before you try it again.") - - def __store_run_lock(self, session: DBSession): + if not existing_run and run_count >= run_limit: + raise RequestFailed( + ErrorCode.GENERAL, + "You reached the maximum number of allowed runs " + f"({run_count}/{run_limit})! " + f"Please remove at least {run_count - run_limit + 1} " + "run(s) before you try again!") + + def _store_run_lock(self): + """Commits a `DBRunLock` for the to-be-stored `Run`, if available.""" + with DBSession(self._product_db) as session: + RunLock(session, self.run_name) \ + .store_run_lock_in_db(self.user_name) + + +class MassStoreRunTask(AbstractTask): + """Executes `MassStoreRun` as a background job.""" + + def __init__(self, token: str, data_path: Path, + package_context, + product_id: int, + input_zip_size: int, + preparation_time_elapsed: float): """ - Store a RunLock record for the given run name into the database. + Creates the `AbstractTask` implementation for + ``massStoreRunAsynchronous()``. + + `preparation_time_elapsed` records how much time was spent by the + input handling that prepared the task. + This time will be added to the total time spent processing the results + in the background. + (The time spent in waiting between task enschedulement and task + execution is not part of the total time.) """ - try: - # If the run can be stored, we need to lock it first. If there is - # already a lock in the database for the given run name which is - # expired and multiple processes are trying to get this entry from - # the database for update we may get the following exception: - # could not obtain lock on row in relation "run_locks" - # This is the reason why we have to wrap this query to a try/except - # block. - run_lock = session.query(RunLock) \ - .filter(RunLock.name == self.__name) \ - .with_for_update(nowait=True).one_or_none() - except (sqlalchemy.exc.OperationalError, - sqlalchemy.exc.ProgrammingError) as ex: - LOG.error("Failed to get run lock for '%s': %s", self.__name, ex) - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - "Someone is already storing to the same run. Please wait " - "while the other storage is finished and try it again.") + super().__init__(token, data_path) + self._package_context = package_context + self._product_id = product_id + self.input_zip_size = input_zip_size + self.time_spent_on_task_preparation = preparation_time_elapsed - if not run_lock: - # If there is no lock record for the given run name, the run - # is not locked -- create a new lock. - run_lock = RunLock(self.__name, self.user_name) - session.add(run_lock) - elif run_lock.has_expired( - db_cleanup.RUN_LOCK_TIMEOUT_IN_DATABASE): - # There can be a lock in the database, which has already - # expired. In this case, we assume that the previous operation - # has failed, and thus, we can re-use the already present lock. - run_lock.touch() - run_lock.username = self.user_name - else: - # In case the lock exists and it has not expired, we must - # consider the run a locked one. - when = run_lock.when_expires( - db_cleanup.RUN_LOCK_TIMEOUT_IN_DATABASE) + def _implementation(self, tm: TaskManager): + try: + with open(self.data_path / "store_configuration.json", 'r', + encoding="utf-8") as cfg_f: + self.store_configuration = json.load(cfg_f) + except Exception: + LOG.error("Invalid or unusable massStoreRunAsynchronous() " + "configuration!") + raise - username = run_lock.username if run_lock.username is not None \ - else "another user" + with DBSession(tm.configuration_database_session_factory) as session: + db_product: Optional[Product] = session.query(Product) \ + .get(self._product_id) + if not db_product: + raise KeyError(f"No product with ID '{self._product_id}'") + + self._product = ServerProduct(db_product.id, + db_product.endpoint, + db_product.display_name, + db_product.connection, + self._package_context, + tm.environment) + + self._product.connect() + if self._product.db_status != DBStatus.OK: + raise EnvironmentError("Database for product " + f"'{self._product.endpoint}' is in " + "a bad shape!") + + m = MassStoreRun(self.data_path / "store_zip", + self._package_context, + tm.configuration_database_session_factory, + self._product, + self.store_configuration["run_name"], + self.store_configuration["store_tag"], + self.store_configuration["client_version"], + self.store_configuration["force_overwrite"], + self.store_configuration["path_prefixes_to_trim"], + self.store_configuration["run_description"], + self.store_configuration["user_name"], + ) + m.store(self.input_zip_size, self.time_spent_on_task_preparation) - LOG.info("Refusing to store into run '%s' as it is locked by " - "%s. Lock will expire at '%s'.", self.__name, username, - when) - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - f"The run named '{self.__name}' is being stored into by " - f"{username}. If the other store operation has failed, this " - f"lock will expire at '{when}'.") - # At any rate, if the lock has been created or updated, commit it - # into the database. - try: - session.commit() - except (sqlalchemy.exc.IntegrityError, - sqlalchemy.orm.exc.StaleDataError) as ex: - # The commit of this lock can fail. - # - # In case two store ops attempt to lock the same run name at the - # same time, committing the lock in the transaction that commits - # later will result in an IntegrityError due to the primary key - # constraint. - # - # In case two store ops attempt to lock the same run name with - # reuse and one of the operation hangs long enough before COMMIT - # so that the other operation commits and thus removes the lock - # record, StaleDataError is raised. In this case, also consider - # the run locked, as the data changed while the transaction was - # waiting, as another run wholly completed. +class MassStoreRun: + """Implementation for ``massStoreRunAsynchronous()``.""" + + # Note: The implementation of this class is called from MassStoreRunTask + # and it is executed in the background, in the context of a Task worker + # process. + # This is the place where complex implementation logic must go, but be + # careful, there is no way to communicate with the user's client anymore! + + # TODO: Poll the task manager at regular points for a cancel signal! + + def __init__(self, + zip_dir: Path, + package_context, + config_db, + product: ServerProduct, + name: str, + tag: Optional[str], + version: Optional[str], + force: bool, + trim_path_prefix_list: Optional[List[str]], + description: Optional[str], + user_name: str, + ): + self._zip_dir = zip_dir + self._name = name + self._tag = tag + self._version = version + self._force = force + self._trim_path_prefixes = trim_path_prefix_list + self._description = description + self._user_name = user_name + self.__config_db = config_db + self.__package_context = package_context + self.__product = product - LOG.info("Run '%s' got locked while current transaction " - "tried to acquire a lock. Considering run as locked.", - self.__name) - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - f"The run named '{self.__name}' is being stored into by " - "another user.") from ex + self.__mips: Dict[str, MetadataInfoParser] = {} + self.__analysis_info: Dict[str, AnalysisInfo] = {} + self.__checker_row_cache: Dict[Tuple[str, str], Checker] = {} + self.__duration: int = 0 + self.__report_count: int = 0 + self.__report_limit: int = 0 + self.__wrong_src_code_comments: List[str] = [] + self.__already_added_report_hashes: Set[str] = set() + self.__new_report_hashes: Dict[str, Tuple] = {} + self.__all_report_checkers: Set[str] = set() + self.__added_reports: List[Tuple[DBReport, Report]] = [] + self.__reports_with_fake_checkers: Dict[ + # Either a DBReport *without* an ID, or the ID of a committed + # DBReport. + str, Tuple[Report, Union[DBReport, int]]] = {} - def __free_run_lock(self, session: DBSession): - """ Remove the lock from the database for the given run name. """ - # Using with_for_update() here so the database (in case it supports - # this operation) locks the lock record's row from any other access. - run_lock = session.query(RunLock) \ - .filter(RunLock.name == self.__name) \ - .with_for_update(nowait=True).one() - session.delete(run_lock) - session.commit() + with DBSession(config_db) as session: + product = session.query(Product).get(self.__product.id) + self.__report_limit = product.report_limit def __store_source_files( self, - source_root: str, + source_root: Path, filename_to_hash: Dict[str, str] ) -> Dict[str, int]: """ Storing file contents from plist. """ @@ -434,10 +674,10 @@ def __store_source_files( file_path_to_id = {} for file_name, file_hash in filename_to_hash.items(): - source_file_path = path_for_fake_root(file_name, source_root) + source_file_path = path_for_fake_root(file_name, str(source_root)) LOG.debug("Storing source file: %s", source_file_path) trimmed_file_path = trim_path_prefixes( - file_name, self.__trim_path_prefixes) + file_name, self._trim_path_prefixes) if not os.path.isfile(source_file_path): # The file was not in the ZIP file, because we already @@ -445,7 +685,7 @@ def __store_source_files( # record in the database or we need to add one. LOG.debug('%s not found or already stored.', trimmed_file_path) - with DBSession(self.__report_server._Session) as session: + with DBSession(self.__product.session_factory) as session: fid = add_file_record( session, trimmed_file_path, file_hash) @@ -458,7 +698,7 @@ def __store_source_files( source_file_path, file_hash) continue - with DBSession(self.__report_server._Session) as session: + with DBSession(self.__product.session_factory) as session: self.__add_file_content(session, source_file_path, file_hash) file_path_to_id[trimmed_file_path] = add_file_record( @@ -468,7 +708,7 @@ def __store_source_files( def __add_blame_info( self, - blame_root: str, + blame_root: Path, filename_to_hash: Dict[str, str] ): """ @@ -480,11 +720,11 @@ def __add_blame_info( .zip file. This function stores blame info even if the corresponding source file is not in the .zip file. """ - with DBSession(self.__report_server._Session) as session: + with DBSession(self.__product.session_factory) as session: for subdir, _, files in os.walk(blame_root): for f in files: - blame_file = os.path.join(subdir, f) - file_path = blame_file[len(blame_root.rstrip("/")):] + blame_file = Path(subdir) / f + file_path = f"/{str(blame_file.relative_to(blame_root))}" blame_info, remote_url, tracking_branch = \ get_blame_file_data(blame_file) @@ -599,8 +839,8 @@ def __store_checker_identifiers(self, checkers: Set[Tuple[str, str]]): while tries < max_tries: tries += 1 try: - LOG.debug("[%s] Begin attempt %d...", self.__name, tries) - with DBSession(self.__report_server._Session) as session: + LOG.debug("[%s] Begin attempt %d...", self._name, tries) + with DBSession(self.__product.session_factory) as session: known_checkers = {(r.analyzer_name, r.checker_name) for r in session .query(Checker.analyzer_name, @@ -608,7 +848,8 @@ def __store_checker_identifiers(self, checkers: Set[Tuple[str, str]]): .all()} for analyzer, checker in \ sorted(all_checkers - known_checkers): - s = self.__context.checker_labels.severity(checker) + s = self.__package_context.checker_labels \ + .severity(checker) s = ttypes.Severity._NAMES_TO_VALUES[s] session.add(Checker(analyzer, checker, s)) LOG.debug("Acquiring ID for checker '%s/%s' " @@ -620,7 +861,7 @@ def __store_checker_identifiers(self, checkers: Set[Tuple[str, str]]): sqlalchemy.exc.ProgrammingError) as ex: LOG.error("Storing checkers of run '%s' failed: %s.\n" "Waiting %d before trying again...", - self.__name, ex, wait_time) + self._name, ex, wait_time) time.sleep(wait_time.total_seconds()) wait_time *= 2 except Exception as ex: @@ -630,10 +871,9 @@ def __store_checker_identifiers(self, checkers: Set[Tuple[str, str]]): traceback.print_exc() raise - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - "Storing the names of the checkers in the run failed due to " - "excessive contention!") + raise ConnectionRefusedError("Storing the names of the checkers in " + "the run failed due to excessive " + "contention!") def __store_analysis_statistics( self, @@ -661,7 +901,7 @@ def __store_analysis_statistics( stats[analyzer_type]["versions"].add(res["version"]) if "failed_sources" in res: - if self.__version == '6.9.0': + if self._version == '6.9.0': stats[analyzer_type]["failed_sources"].add( 'Unavailable in CodeChecker 6.9.0!') else: @@ -757,89 +997,82 @@ def __add_or_update_run( By default updates the results if name already exists. Using the force flag removes existing analysis results for a run. """ - try: - LOG.debug("Adding run '%s'...", self.__name) + LOG.debug("Adding run '%s'...", self._name) + + run = session.query(Run) \ + .filter(Run.name == self._name) \ + .one_or_none() + + update_run = True + if run and self._force: + # Clean already collected results. + if not run.can_delete: + # Deletion is already in progress. + msg = f"Can't delete {run.id}" + LOG.debug(msg) + raise EnvironmentError(msg) + + LOG.info('Removing previous analysis results...') + session.delete(run) + # Not flushing after delete leads to a constraint violation + # error later, when adding run entity with the same name as + # the old one. + session.flush() - run = session.query(Run) \ - .filter(Run.name == self.__name) \ - .one_or_none() + checker_run = Run(self._name, self._version) + session.add(checker_run) + session.flush() + run_id = checker_run.id - update_run = True - if run and self.__force: - # Clean already collected results. - if not run.can_delete: - # Deletion is already in progress. - msg = f"Can't delete {run.id}" - LOG.debug(msg) - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - msg) - - LOG.info('Removing previous analysis results...') - session.delete(run) - # Not flushing after delete leads to a constraint violation - # error later, when adding run entity with the same name as - # the old one. - session.flush() - - checker_run = Run(self.__name, self.__version) - session.add(checker_run) - session.flush() - run_id = checker_run.id - - elif run: - # There is already a run, update the results. - run.date = datetime.now() - run.duration = -1 - session.flush() - run_id = run.id - else: - # There is no run create new. - checker_run = Run(self.__name, self.__version) - session.add(checker_run) - session.flush() - run_id = checker_run.id - update_run = False - - # Add run to the history. - LOG.debug("Adding run history.") - - if self.__tag is not None: - run_history = session.query(RunHistory) \ - .filter(RunHistory.run_id == run_id, - RunHistory.version_tag == self.__tag) \ - .one_or_none() - - if run_history: - run_history.version_tag = None - session.add(run_history) - - cc_versions = set() - for mip in self.__mips.values(): - if mip.cc_version: - cc_versions.add(mip.cc_version) - - cc_version = '; '.join(cc_versions) if cc_versions else None - run_history = RunHistory( - run_id, self.__tag, self.user_name, run_history_time, - cc_version, self.__description) - - session.add(run_history) + elif run: + # There is already a run, update the results. + run.date = datetime.now() + run.duration = -1 session.flush() + run_id = run.id + else: + # There is no run create new. + checker_run = Run(self._name, self._version) + session.add(checker_run) + session.flush() + run_id = checker_run.id + update_run = False + + # Add run to the history. + LOG.debug("Adding run history.") - LOG.debug("Adding run done.") + if self._tag is not None: + run_history = session.query(RunHistory) \ + .filter(RunHistory.run_id == run_id, + RunHistory.version_tag == self._tag) \ + .one_or_none() - self.__store_analysis_statistics(session, run_history.id) - self.__store_analysis_info(session, run_history) + if run_history: + run_history.version_tag = None + session.add(run_history) - session.flush() - LOG.debug("Storing analysis statistics done.") + cc_versions = set() + for mip in self.__mips.values(): + if mip.cc_version: + cc_versions.add(mip.cc_version) - return run_id, update_run - except Exception as ex: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.GENERAL, - str(ex)) + cc_version = '; '.join(cc_versions) if cc_versions else None + run_history = RunHistory( + run_id, self._tag, self._user_name, run_history_time, + cc_version, self._description) + + session.add(run_history) + session.flush() + + LOG.debug("Adding run done.") + + self.__store_analysis_statistics(session, run_history.id) + self.__store_analysis_info(session, run_history) + + session.flush() + LOG.debug("Storing analysis statistics done.") + + return run_id, update_run def __get_checker(self, session: DBSession, @@ -879,49 +1112,43 @@ def __add_report( fixed_at: Optional[datetime] = None ) -> int: """ Add report to the database. """ - try: - checker = self.__checker_for_report(session, report) + checker = self.__checker_for_report(session, report) + if not checker: + # It would be too easy to create a 'Checker' instance with the + # observed data right here, but __add_report() is called in + # the context of the *BIG* TRANSACTION which has all the + # reports of the entire store pending. Losing all that + # information on a potential UNIQUE CONSTRAINT violation due + # to multiple concurrent massStoreRun()s trying to store the + # same checker ID which was never seen in a 'metadata.json' is + # not worth it. + checker = self.__get_checker(session, + FakeChecker[0], FakeChecker[1]) if not checker: - # It would be too easy to create a 'Checker' instance with the - # observed data right here, but __add_report() is called in - # the context of the *BIG* TRANSACTION which has all the - # reports of the entire store pending. Losing all that - # information on a potential UNIQUE CONSTRAINT violation due - # to multiple concurrent massStoreRun()s trying to store the - # same checker ID which was never seen in a 'metadata.json' is - # not worth it. - checker = self.__get_checker(session, - FakeChecker[0], FakeChecker[1]) - if not checker: - LOG.fatal("Psuedo-checker '%s/%s' has no " - "identity in the database, even though " - "__store_checker_identifiers() should have " - "always preemptively created it!", - FakeChecker[0], FakeChecker[1]) - raise KeyError(FakeChecker[1]) - - db_report = DBReport( - file_path_to_id[report.file.path], run_id, report.report_hash, - checker, report.line, report.column, - len(report.bug_path_events), report.message, detection_status, - review_status.status, review_status.author, - review_status.message, run_history_time, - review_status.in_source, detection_time, fixed_at) - if analysis_info: - db_report.analysis_info.append(analysis_info) - - session.add(db_report) - self.__added_reports.append((db_report, report)) - if db_report.checker.checker_name == FakeChecker[1]: - self.__reports_with_fake_checkers[report_path_hash] = \ - (report, db_report) - - return db_report.id - - except Exception as ex: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.GENERAL, - str(ex)) + LOG.fatal("Psuedo-checker '%s/%s' has no " + "identity in the database, even though " + "__store_checker_identifiers() should have " + "always preemptively created it!", + FakeChecker[0], FakeChecker[1]) + raise KeyError(FakeChecker[1]) + + db_report = DBReport( + file_path_to_id[report.file.path], run_id, report.report_hash, + checker, report.line, report.column, + len(report.bug_path_events), report.message, detection_status, + review_status.status, review_status.author, + review_status.message, run_history_time, + review_status.in_source, detection_time, fixed_at) + if analysis_info: + db_report.analysis_info.append(analysis_info) + + session.add(db_report) + self.__added_reports.append((db_report, report)) + if db_report.checker.checker_name == FakeChecker[1]: + self.__reports_with_fake_checkers[report_path_hash] = \ + (report, db_report) + + return db_report.id def __get_faked_checkers(self) \ -> Set[Tuple[str, str]]: @@ -966,78 +1193,67 @@ def __realise_fake_checkers(self, session): so all it does is upgrade the 'checker_id' FOREIGN KEY field to point at the real checker. """ - try: - grouped_by_checker: Dict[Tuple[str, str], List[int]] = \ - defaultdict(list) - for _, (report, db_id) in \ - self.__reports_with_fake_checkers.items(): - checker: Tuple[str, str] = checker_name_for_report(report) - grouped_by_checker[checker].append(cast(int, db_id)) - - for chk, report_ids in grouped_by_checker.items(): - analyzer_name, checker_name = chk - chk_obj = cast(Checker, self.__get_checker(session, - analyzer_name, - checker_name)) - session.query(DBReport) \ - .filter(DBReport.id.in_(report_ids)) \ - .update({"checker_id": chk_obj.id}, - synchronize_session=False) - except Exception as ex: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - str(ex)) + grouped_by_checker: Dict[Tuple[str, str], List[int]] = \ + defaultdict(list) + for _, (report, db_id) in \ + self.__reports_with_fake_checkers.items(): + checker: Tuple[str, str] = checker_name_for_report(report) + grouped_by_checker[checker].append(cast(int, db_id)) + + for chk, report_ids in grouped_by_checker.items(): + analyzer_name, checker_name = chk + chk_obj = cast(Checker, self.__get_checker(session, + analyzer_name, + checker_name)) + session.query(DBReport) \ + .filter(DBReport.id.in_(report_ids)) \ + .update({"checker_id": chk_obj.id}, + synchronize_session=False) def __add_report_context(self, session, file_path_to_id): - try: - for db_report, report in self.__added_reports: - LOG.debug("Storing bug path positions.") - for i, p in enumerate(report.bug_path_positions): - session.add(BugReportPoint( - p.range.start_line, p.range.start_col, - p.range.end_line, p.range.end_col, - i, file_path_to_id[p.file.path], db_report.id)) - - LOG.debug("Storing bug path events.") - for i, event in enumerate(report.bug_path_events): - session.add(BugPathEvent( - event.range.start_line, event.range.start_col, - event.range.end_line, event.range.end_col, - i, event.message, file_path_to_id[event.file.path], - db_report.id)) - - LOG.debug("Storing notes.") - for note in report.notes: - data_type = report_extended_data_type_str( - ttypes.ExtendedReportDataType.NOTE) - - session.add(ExtendedReportData( - note.range.start_line, note.range.start_col, - note.range.end_line, note.range.end_col, - note.message, file_path_to_id[note.file.path], - db_report.id, data_type)) - - LOG.debug("Storing macro expansions.") - for macro in report.macro_expansions: - data_type = report_extended_data_type_str( - ttypes.ExtendedReportDataType.MACRO) - - session.add(ExtendedReportData( - macro.range.start_line, macro.range.start_col, - macro.range.end_line, macro.range.end_col, - macro.message, file_path_to_id[macro.file.path], - db_report.id, data_type)) - - if report.annotations: - self.__validate_and_add_report_annotations( - session, db_report.id, report.annotations) + for db_report, report in self.__added_reports: + LOG.debug("Storing bug path positions.") + for i, p in enumerate(report.bug_path_positions): + session.add(BugReportPoint( + p.range.start_line, p.range.start_col, + p.range.end_line, p.range.end_col, + i, file_path_to_id[p.file.path], db_report.id)) + + LOG.debug("Storing bug path events.") + for i, event in enumerate(report.bug_path_events): + session.add(BugPathEvent( + event.range.start_line, event.range.start_col, + event.range.end_line, event.range.end_col, + i, event.message, file_path_to_id[event.file.path], + db_report.id)) + + LOG.debug("Storing notes.") + for note in report.notes: + data_type = report_extended_data_type_str( + ttypes.ExtendedReportDataType.NOTE) + + session.add(ExtendedReportData( + note.range.start_line, note.range.start_col, + note.range.end_line, note.range.end_col, + note.message, file_path_to_id[note.file.path], + db_report.id, data_type)) + + LOG.debug("Storing macro expansions.") + for macro in report.macro_expansions: + data_type = report_extended_data_type_str( + ttypes.ExtendedReportDataType.MACRO) + + session.add(ExtendedReportData( + macro.range.start_line, macro.range.start_col, + macro.range.end_line, macro.range.end_col, + macro.message, file_path_to_id[macro.file.path], + db_report.id, data_type)) + + if report.annotations: + self.__validate_and_add_report_annotations( + session, db_report.id, report.annotations) - session.flush() - - except Exception as ex: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.GENERAL, - str(ex)) + session.flush() def __process_report_file( self, @@ -1074,7 +1290,7 @@ def get_missing_file_ids(report: Report) -> List[str]: for report in reports: self.__report_count += 1 - report.trim_path_prefixes(self.__trim_path_prefixes) + report.trim_path_prefixes(self._trim_path_prefixes) missing_ids_for_files = get_missing_file_ids(report) if missing_ids_for_files: @@ -1117,7 +1333,7 @@ def get_missing_file_ids(report: Report) -> List[str]: except ValueError as err: self.__wrong_src_code_comments.append(str(err)) - review_status.author = self.user_name + review_status.author = self._user_name review_status.date = run_history_time # False positive and intentional reports are considered as closed @@ -1181,24 +1397,17 @@ def __validate_and_add_report_annotations( try: allowed_annotations[key]["func"](value) session.add(ReportAnnotations(report_id, key, value)) - except KeyError: - # pylint: disable=raise-missing-from - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.REPORT_FORMAT, - f"'{key}' is not an allowed report annotation.", - allowed_annotations.keys()) - except ValueError: - # pylint: disable=raise-missing-from - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.REPORT_FORMAT, - f"'{value}' has wrong format. '{key}' annotations must be " - f"'{allowed_annotations[key]['display']}'.") - - def __get_report_limit_for_product(self): - with DBSession(self.__config_database) as session: - product = session.query(Product).get(self.__product.id) - if product.report_limit: - self.__report_limit = product.report_limit + except KeyError as ke: + raise TypeError(f"'{key}' is not an allowed report " + "annotation. " + "The allowed annotations are: " + f"{allowed_annotations.keys()}") \ + from ke + except ValueError as ve: + raise ValueError(f"'{value}' is in a wrong format! " + f"'{key}' annotations must be " + f"'{allowed_annotations[key]['display']}'.") \ + from ve def __check_report_count(self): """ @@ -1210,13 +1419,7 @@ def __check_report_count(self): LOG.error("The number of reports in the given report folder is " + "larger than the allowed." + f"The limit: {self.__report_limit}!") - extra_info = [ - "report_limit", - f"limit:{self.__report_limit}" - ] - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes. - ErrorCode.GENERAL, + raise OverflowError( "**Report Limit Exceeded** " + "This report folder cannot be stored because the number of " + "reports in the result folder is too high. Usually noisy " + @@ -1226,14 +1429,13 @@ def __check_report_count(self): "counts. Disable checkers that have generated an excessive " + "number of reports and then rerun the analysis to be able " + "to store the results on the server. " + - f"Limit: {self.__report_limit}", - extra_info) + f"Limit: {self.__report_limit}") def __store_reports( self, session: DBSession, - report_dir: str, - source_root: str, + report_dir: Path, + source_root: Path, run_id: int, file_path_to_id: Dict[str, int], run_history_time: datetime @@ -1241,11 +1443,11 @@ def __store_reports( """ Parse up and store the plist report files. """ def get_skip_handler( - report_dir: str + report_dir: Path ) -> skiplist_handler.SkipListHandler: """ Get a skip list handler based on the given report directory.""" - skip_file_path = os.path.join(report_dir, 'skip_file') - if not os.path.exists(skip_file_path): + skip_file_path = report_dir / "skip_file" + if not skip_file_path.exists(): return skiplist_handler.SkipListHandler() LOG.debug("Pocessing skip file %s", skip_file_path) @@ -1282,9 +1484,8 @@ def get_skip_handler( for root_dir_path, _, report_file_paths in os.walk(report_dir): LOG.debug("Get reports from '%s' directory", root_dir_path) - skip_handler = get_skip_handler(root_dir_path) - - review_status_handler = ReviewStatusHandler(source_root) + skip_handler = get_skip_handler(Path(root_dir_path)) + review_status_handler = ReviewStatusHandler(str(source_root)) review_status_cfg = \ os.path.join(root_dir_path, 'review_status.yaml') @@ -1346,7 +1547,7 @@ def get_skip_handler( session.flush() - LOG.info("[%s] Processed %d analyzer result file(s).", self.__name, + LOG.info("[%s] Processed %d analyzer result file(s).", self._name, processed_result_file_count) # If a checker was found in a plist file it can not be disabled so we @@ -1377,8 +1578,8 @@ def get_skip_handler( report.fixed_at = run_history_time if reports_to_delete: - self.__report_server._removeReports( - session, list(reports_to_delete)) + from .report_server import remove_reports + remove_reports(session, reports_to_delete) def finish_checker_run( self, @@ -1401,153 +1602,126 @@ def finish_checker_run( return False - def store(self) -> int: - """ Store run results to the server. """ + def store(self, + original_zip_size: int, + time_spent_on_task_preparation: float): + """Store run results to the server.""" start_time = time.time() - # Check constraints of the run. - self.__check_run_limit() - - with DBSession(self.__report_server._Session) as session: - self.__store_run_lock(session) - try: - with TemporaryDirectory( - dir=self.__context.codechecker_workspace - ) as zip_dir: - with LogTask(run_name=self.__name, - message="Unzip storage file"): - zip_size = unzip(self.__b64zip, zip_dir) - - if zip_size == 0: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes. - ErrorCode.GENERAL, - "The received zip file content is empty!") - - LOG.debug("Using unzipped folder '%s'", zip_dir) - - source_root = os.path.join(zip_dir, 'root') - blame_root = os.path.join(zip_dir, 'blame') - report_dir = os.path.join(zip_dir, 'reports') - content_hash_file = os.path.join( - zip_dir, 'content_hashes.json') - - filename_to_hash = load_json(content_hash_file, {}) - - with LogTask(run_name=self.__name, - message="Store source files"): - LOG.info("[%s] Storing %d source file(s).", self.__name, - len(filename_to_hash.keys())) - file_path_to_id = self.__store_source_files( - source_root, filename_to_hash) - self.__add_blame_info(blame_root, filename_to_hash) - - run_history_time = datetime.now() - - # Parse all metadata information from the report directory. - with LogTask(run_name=self.__name, - message="Parse 'metadata.json's"): - for root_dir_path, _, _ in os.walk(report_dir): - metadata_file_path = os.path.join( - root_dir_path, 'metadata.json') - - self.__mips[root_dir_path] = \ - MetadataInfoParser(metadata_file_path) - - with LogTask(run_name=self.__name, - message="Store look-up ID for checkers in " - "'metadata.json'"): - checkers_in_metadata = { - (analyzer, checker) - for metadata in self.__mips.values() - for analyzer in metadata.analyzers - for checker - in metadata.checkers.get(analyzer, {}).keys()} - self.__store_checker_identifiers(checkers_in_metadata) - - try: - # This session's transaction buffer stores the actual - # run data into the database. - with DBSession(self.__report_server._Session) as session, \ - RunLocking(session, self.__name): - # Actual store operation begins here. - run_id, update_run = self.__add_or_update_run( - session, run_history_time) - - with LogTask(run_name=self.__name, - message="Store reports"): - self.__store_reports( - session, report_dir, source_root, run_id, - file_path_to_id, run_history_time) - - session.commit() - self.__load_report_ids_for_reports_with_fake_checkers( - session) + LOG.debug("Using unzipped folder '%s'", self._zip_dir) + + source_root = self._zip_dir / "root" + blame_root = self._zip_dir / "blame" + report_dir = self._zip_dir / "reports" + filename_to_hash = load_json( + self._zip_dir / "content_hashes.json", {}) + + # Store information that is "global" on the product database level. + with StepLog(self._name, "Store source files"): + LOG.info("[%s] Storing %d source file(s).", self._name, + len(filename_to_hash.keys())) + file_path_to_id = self.__store_source_files( + source_root, filename_to_hash) + self.__add_blame_info(blame_root, filename_to_hash) + + run_history_time = datetime.now() + + with StepLog(self._name, "Parse 'metadata.json's"): + for root_dir_path, _, _ in os.walk(report_dir): + metadata_file_path = os.path.join( + root_dir_path, 'metadata.json') + + self.__mips[root_dir_path] = \ + MetadataInfoParser(metadata_file_path) + + with StepLog(self._name, + "Store look-up ID for checkers in 'metadata.json'"): + checkers_in_metadata = { + (analyzer, checker) + for metadata in self.__mips.values() + for analyzer in metadata.analyzers + for checker + in metadata.checkers.get(analyzer, {}).keys()} + self.__store_checker_identifiers(checkers_in_metadata) + + try: + # This session's transaction buffer stores the actual run data + # into the database. + with DBSession(self.__product.session_factory) as session, \ + RunLock(session, self._name): + run_id, update_run = self.__add_or_update_run( + session, run_history_time) + + with StepLog(self._name, "Store 'reports'"): + self.__store_reports( + session, report_dir, source_root, run_id, + file_path_to_id, run_history_time) + session.commit() + self.__load_report_ids_for_reports_with_fake_checkers( + session) + + if self.__reports_with_fake_checkers: + with StepLog( + self._name, + "Get look-up IDs for checkers not present in " + "'metadata.json'"): + additional_checkers = self.__get_faked_checkers() + # __store_checker_identifiers() has its own + # TRANSACTION! + self.__store_checker_identifiers( + additional_checkers) + + with DBSession(self.__product.session_factory) as session, \ + RunLock(session, self._name): + # The data of the run has been successfully committed + # into the database. Deal with post-processing issues + # that could only be done after-the-fact. if self.__reports_with_fake_checkers: - with LogTask(run_name=self.__name, - message="Get look-up ID for checkers " - "not present in 'metadata.json'"): - additional_checkers = self.__get_faked_checkers() - # __store_checker_identifiers() has its own - # TRANSACTION! - self.__store_checker_identifiers( - additional_checkers) - - with DBSession(self.__report_server._Session) as session, \ - RunLocking(session, self.__name): - # The data of the run has been successfully committed - # into the database. Deal with post-processing issues - # that could only be done after-the-fact. - if self.__reports_with_fake_checkers: - with LogTask(run_name=self.__name, - message="Fix-up report-to-checker " - "associations"): - self.__realise_fake_checkers(session) - - self.finish_checker_run(session, run_id) - session.commit() - - # If it's a run update, do not increment the number - # of runs of the current product. - inc_num_of_runs = 1 if not update_run else None - - self.__report_server._set_run_data_for_curr_product( - inc_num_of_runs, run_history_time) - - runtime = round(time.time() - start_time, 2) - zip_size_kb = round(zip_size / 1024) - - tag_desc = "" - if self.__tag: - tag_desc = f", under tag '{self.__tag}'" - - LOG.info("'%s' stored results (%s KB " - "/decompressed/) to run '%s' (id: %d) %s in " - "%s seconds.", self.user_name, - zip_size_kb, self.__name, run_id, tag_desc, - runtime) - - iso_start_time = datetime.fromtimestamp( - start_time).isoformat() - - log_msg = f"{iso_start_time}, " +\ - f"{runtime}s, " +\ - f'"{self.__product.name}", ' +\ - f'"{self.__name}", ' +\ - f"{zip_size_kb}KB, " +\ - f"{self.__report_count}, " +\ - f"{run_id}" - - STORE_TIME_LOG.info(log_msg) - - return run_id - except (sqlalchemy.exc.OperationalError, - sqlalchemy.exc.ProgrammingError) as ex: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.DATABASE, - f"Storing reports to the database failed: {ex}") + with StepLog(self._name, + "Fix-up report-to-checker associations"): + self.__realise_fake_checkers(session) + + self.finish_checker_run(session, run_id) + session.commit() + + end_time = time.time() + + # If the current store() updated an existing run, do not + # increment the number of runs saved for the product. + self.__product.set_cached_run_data( + self.__config_db, + number_of_runs_change=(0 if update_run else 1), + last_store_date=run_history_time) + + run_time: float = (end_time - start_time) + \ + time_spent_on_task_preparation + zip_size_kib: float = original_zip_size / 1024 + + LOG.info("'%s' stored results (decompressed size: %.2f KiB) " + "to run '%s' (ID: %d%s) in %.2f seconds.", + self._user_name, zip_size_kib, self._name, run_id, + f", under tag '{self._tag}'" if self._tag else "", + run_time) + + iso_start_time = datetime.fromtimestamp(start_time) \ + .isoformat() + + log_msg = f"{iso_start_time}, " \ + f"{round(run_time, 2)}s, " \ + f'"{self.__product.name}", ' \ + f'"{self._name}", ' \ + f"{round(zip_size_kib)}KiB, " \ + f"{self.__report_count}, " \ + f"{run_id}" + + STORE_TIME_LOG.info(log_msg) + except (sqlalchemy.exc.OperationalError, + sqlalchemy.exc.ProgrammingError) as ex: + LOG.error("Database error! Storing reports to the " + "database failed: %s", ex) + raise except Exception as ex: LOG.error("Failed to store results: %s", ex) import traceback @@ -1560,10 +1734,17 @@ def store(self) -> int: # (If the failure is undetectable, the coded grace period expiry # of the lock will allow further store operations to the given # run name.) - with DBSession(self.__report_server._Session) as session: - self.__free_run_lock(session) + with DBSession(self.__product.session_factory) as session: + RunLock(session, self._name).drop_run_lock_from_db() if self.__wrong_src_code_comments: - raise codechecker_api_shared.ttypes.RequestFailed( - codechecker_api_shared.ttypes.ErrorCode.SOURCE_FILE, - self.__wrong_src_code_comments) + wrong_files_as_table = twodim.to_str( + "table", + ["File", "Line", "Checker name"], + [wrong_comment.split('|', 3) + for wrong_comment in self.__wrong_src_code_comments]) + + raise ValueError("One or more source files contained invalid " + "source code comments! " + "Failed to set review statuses.\n\n" + f"{wrong_files_as_table}") diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py index 2348708b81..3248146761 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -21,8 +21,8 @@ from copy import deepcopy from collections import OrderedDict, defaultdict, namedtuple -from datetime import datetime, timedelta -from typing import Any, Dict, List, Optional, Set, Tuple +from datetime import datetime +from typing import Any, Collection, Dict, List, Optional, Set, Tuple import sqlalchemy from sqlalchemy.sql.expression import or_, and_, not_, func, \ @@ -1340,13 +1340,26 @@ def get_is_opened_case(subquery): ) +def remove_reports(session: DBSession, + report_ids: Collection, + chunk_size: int = SQLITE_MAX_VARIABLE_NUMBER): + """ + Removes `Report`s in chunks. + """ + for r_ids in util.chunks(iter(report_ids), chunk_size): + session.query(Report) \ + .filter(Report.id.in_(r_ids)) \ + .delete(synchronize_session=False) + + class ThriftRequestHandler: """ Connect to database and handle thrift client requests. """ def __init__(self, - manager, + session_manager, + task_manager, Session, product, auth_session, @@ -1359,7 +1372,8 @@ def __init__(self, raise ValueError("Cannot initialize request handler without " "a product to serve.") - self._manager = manager + self._manager = session_manager + self._task_manager = task_manager self._product = product self._auth_session = auth_session self._config_database = config_database @@ -1377,34 +1391,6 @@ def _get_username(self): """ return self._auth_session.user if self._auth_session else "Anonymous" - def _set_run_data_for_curr_product( - self, - inc_num_of_runs: Optional[int], - latest_storage_date: Optional[datetime] = None - ): - """ - Increment the number of runs related to the current product with the - given value and set the latest storage date. - """ - values = {} - - if inc_num_of_runs is not None: - values["num_of_runs"] = Product.num_of_runs + inc_num_of_runs - # FIXME: This log is likely overkill. - LOG.info("Run counter in the config database was %s by %i.", - 'increased' if inc_num_of_runs >= 0 else 'decreased', - abs(inc_num_of_runs)) - - if latest_storage_date is not None: - values["latest_storage_date"] = latest_storage_date - - with DBSession(self._config_database) as session: - session.query(Product) \ - .filter(Product.id == self._product.id) \ - .update(values) - - session.commit() - def __require_permission(self, required): """ Helper method to raise an UNAUTHORIZED exception if the user does not @@ -3595,16 +3581,6 @@ def removeRunResults(self, run_ids): failed = True return not failed - def _removeReports(self, session, report_ids, - chunk_size=SQLITE_MAX_VARIABLE_NUMBER): - """ - Removing reports in chunks. - """ - for r_ids in util.chunks(iter(report_ids), chunk_size): - session.query(Report) \ - .filter(Report.id.in_(r_ids)) \ - .delete(synchronize_session=False) - @exc_to_thrift_reqfail @timeit def removeRunReports(self, run_ids, report_filter, cmp_data): @@ -3634,7 +3610,7 @@ def removeRunReports(self, run_ids, report_filter, cmp_data): reports_to_delete = [r[0] for r in q] if reports_to_delete: - self._removeReports(session, reports_to_delete) + remove_reports(session, reports_to_delete) session.commit() session.close() @@ -3706,9 +3682,9 @@ def removeRun(self, run_id, run_filter): LOG.info("Runs '%s' were removed by '%s'.", "', '".join(runs), self._get_username()) - # Decrement the number of runs but do not update the latest storage - # date. - self._set_run_data_for_curr_product(-1 * deleted_run_cnt) + self._product.set_cached_run_data( + self._config_database, + number_of_runs_change=-1 * deleted_run_cnt) # Remove unused comments and unused analysis info from the database. # Originally db_cleanup.remove_unused_data() was used here which @@ -3893,14 +3869,69 @@ def getMissingContentHashesForBlameInfo(self, file_hashes): @exc_to_thrift_reqfail @timeit - def massStoreRun(self, name, tag, version, b64zip, force, - trim_path_prefixes, description): + def massStoreRun(self, + name: str, + tag: Optional[str], + version: str, + b64zip: str, + force: bool, + trim_path_prefixes: Optional[List[str]], + description: Optional[str]) -> int: self.__require_store() - - from codechecker_server.api.mass_store_run import MassStoreRun - m = MassStoreRun(self, name, tag, version, b64zip, force, - trim_path_prefixes, description) - return m.store() + if not name: + raise ValueError("A run name is needed to know where to store!") + + from .mass_store_run import MassStoreRunInputHandler, MassStoreRunTask + ih = MassStoreRunInputHandler(self._manager, + self._config_database, + self._Session, + self._task_manager, + self._context, + self._product.id, + name, + description, + tag, + version, + force, + trim_path_prefixes, + b64zip, + self._get_username()) + ih.check_store_input_validity_at_face_value() + m: MassStoreRunTask = ih.create_mass_store_task(False) + self._task_manager.push_task(m) + + LOG.info("massStoreRun(): Running as '%s' ...", m.token) + + # To be compatible with older (<= 6.24, API <= 6.58) clients which + # may keep using the old API endpoint, simulate awaiting the + # background task in the API handler. + while True: + time.sleep(5) + t = self._task_manager.get_task_record(m.token) + if t.is_in_terminated_state: + if t.status == "failed": + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.GENERAL, + "massStoreRun()'s processing failed. Here follow " + f"the details:\n\n{t.comments}") + if t.status == "cancelled": + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.GENERAL, + "Server administrators cancelled the processing of " + "the massStoreRun() request!") + break + + # Prior to CodeChecker 6.25.0 (API v6.59), massStoreRun() was + # completely synchronous and blocking, and the implementation of the + # storage logic returned the ID of the run that was stored by the + # call. + # massStoreRun() was implemented in + # commit 2b29d787599da0318cd23dbe816377b9bce7236c (September 2017), + # replacing the previously used (and then completely removed!) + # addCheckerRun() function, which also returned the run's ID. + # The official client implementation stopped using this returned value + # from the moment of massStoreRun()'s implementation. + return -1 @exc_to_thrift_reqfail @timeit diff --git a/web/server/codechecker_server/product.py b/web/server/codechecker_server/product.py new file mode 100644 index 0000000000..3c18de4339 --- /dev/null +++ b/web/server/codechecker_server/product.py @@ -0,0 +1,236 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +The in-memory representation and access methods for querying and mutating a +"Product": a separate and self-contained database and entity containing +analysis results and associated information, which a CodeChecker server can +connect to. +""" +from datetime import datetime +from typing import Optional + +from sqlalchemy.orm import sessionmaker + +from codechecker_api_shared.ttypes import DBStatus + +from codechecker_common.logger import get_logger + +from .database import database, db_cleanup +from .database.config_db_model import Product as DBProduct +from .database.database import DBSession +from .database.run_db_model import \ + IDENTIFIER as RUN_META, \ + Run, RunLock + + +LOG = get_logger("server") + + +class Product: + """ + Represents a product, which is a distinct storage of analysis reports in + a separate database (and database connection) with its own access control. + """ + + # The amount of SECONDS that need to pass after the last unsuccessful + # connect() call so the next could be made. + CONNECT_RETRY_TIMEOUT = 300 + + def __init__(self, id_: int, endpoint: str, display_name: str, + connection_string: str, context, check_env): + """ + Set up a new managed product object for the configuration given. + """ + self.__id = id_ + self.__endpoint = endpoint + self.__display_name = display_name + self.__connection_string = connection_string + self.__driver_name = None + self.__context = context + self.__check_env = check_env + self.__engine = None + self.__session = None + self.__db_status = DBStatus.MISSING + + self.__last_connect_attempt = None + + @property + def id(self): + return self.__id + + @property + def endpoint(self): + """ + Returns the accessible URL endpoint of the product. + """ + return self.__endpoint + + @property + def name(self): + """ + Returns the display name of the product. + """ + return self.__display_name + + @property + def session_factory(self): + """ + Returns the session maker on this product's database engine which + can be used to initiate transactional connections. + """ + return self.__session + + @property + def driver_name(self): + """ + Returns the name of the sql driver (sqlite, postgres). + """ + return self.__driver_name + + @property + def db_status(self): + """ + Returns the status of the database which belongs to this product. + Call connect to update it. + """ + return self.__db_status + + @property + def last_connection_failure(self): + """ + Returns the reason behind the last executed connection attempt's + failure. + """ + return self.__last_connect_attempt[1] if self.__last_connect_attempt \ + else None + + def connect(self, init_db=False): + """ + Initiates the actual connection to the database configured for the + product. + + Each time the connect is called the db_status is updated. + """ + LOG.debug("Checking '%s' database.", self.endpoint) + + sql_server = database.SQLServer.from_connection_string( + self.__connection_string, + self.__endpoint, + RUN_META, + self.__context.run_migration_root, + interactive=False, + env=self.__check_env) + + if isinstance(sql_server, database.PostgreSQLServer): + self.__driver_name = 'postgresql' + elif isinstance(sql_server, database.SQLiteDatabase): + self.__driver_name = 'sqlite' + + try: + LOG.debug("Trying to connect to the database") + + # Create the SQLAlchemy engine. + self.__engine = sql_server.create_engine() + LOG.debug(self.__engine) + + self.__session = sessionmaker(bind=self.__engine) + + self.__engine.execute('SELECT 1') + self.__db_status = sql_server.check_schema() + self.__last_connect_attempt = None + + if self.__db_status == DBStatus.SCHEMA_MISSING and init_db: + LOG.debug("Initializing new database schema.") + self.__db_status = sql_server.connect(init_db) + + except Exception as ex: + LOG.exception("The database for product '%s' cannot be" + " connected to.", self.endpoint) + self.__db_status = DBStatus.FAILED_TO_CONNECT + self.__last_connect_attempt = (datetime.now(), str(ex)) + + def get_details(self): + """ + Get details for a product from the database. + + It may throw different error messages depending on the used SQL driver + adapter in case of connection error. + """ + with DBSession(self.session_factory) as run_db_session: + run_locks = run_db_session.query(RunLock.name) \ + .filter(RunLock.locked_at.isnot(None)) \ + .all() + + runs_in_progress = set(run_lock[0] for run_lock in run_locks) + + num_of_runs = run_db_session.query(Run).count() + + latest_store_to_product = "" + if num_of_runs: + last_updated_run = run_db_session.query(Run) \ + .order_by(Run.date.desc()) \ + .limit(1) \ + .one_or_none() + + latest_store_to_product = last_updated_run.date + + return num_of_runs, runs_in_progress, latest_store_to_product + + def teardown(self): + """ + Disposes the database connection to the product's backend. + """ + if self.__db_status == DBStatus.FAILED_TO_CONNECT: + return + + self.__engine.dispose() + + self.__session = None + self.__engine = None + + def cleanup_run_db(self): + """ + Cleanup the run database which belongs to this product. + """ + LOG.info("[%s] Garbage collection started...", self.endpoint) + + db_cleanup.remove_expired_data(self) + db_cleanup.remove_unused_data(self) + db_cleanup.update_contextual_data(self, self.__context) + + LOG.info("[%s] Garbage collection finished.", self.endpoint) + return True + + def set_cached_run_data(self, + config_db_session_factory, + number_of_runs_change: Optional[int] = None, + last_store_date: Optional[datetime] = None): + """ + Update the configuration database row for the current `Product` + for the keys that contain cached summaries of what would otherwise + be queriable from the product's database. + """ + updates = {} + + if number_of_runs_change: + updates["num_of_runs"] = DBProduct.num_of_runs \ + + number_of_runs_change + LOG.info("%s: Changing 'num_of_runs' in CONFIG database by %s%i.", + self.__endpoint, + '+' if number_of_runs_change > 0 else '-', + abs(number_of_runs_change)) + + if last_store_date: + updates["latest_storage_date"] = last_store_date + + if updates: + with DBSession(config_db_session_factory) as session: + session.query(DBProduct) \ + .filter(DBProduct.id == self.__id) \ + .update(updates) + session.commit() diff --git a/web/server/codechecker_server/server.py b/web/server/codechecker_server/server.py index f10f28291e..247c66c3ee 100644 --- a/web/server/codechecker_server/server.py +++ b/web/server/codechecker_server/server.py @@ -13,7 +13,6 @@ import atexit from collections import Counter -import datetime from functools import partial from hashlib import sha256 from http.server import HTTPServer, SimpleHTTPRequestHandler @@ -68,14 +67,15 @@ from .api.server_info_handler import \ ThriftServerInfoHandler as ServerInfoHandler_v6 from .api.tasks import ThriftTaskHandler as TaskHandler_v6 -from .database import database, db_cleanup from .database.config_db_model import Product as ORMProduct, \ Configuration as ORMConfiguration from .database.database import DBSession -from .database.run_db_model import IDENTIFIER as RUN_META, Run, RunLock +from .database.run_db_model import Run +from .product import Product +from .session_manager import SessionManager, SESSION_COOKIE_NAME from .task_executors.main import executor as background_task_executor from .task_executors.task_manager import \ - TaskManager as BackgroundTaskManager, drop_all_incomplete_tasks + TaskManager as BackgroundTaskManager LOG = get_logger('server') @@ -421,6 +421,7 @@ def do_POST(self): acc_handler = ReportHandler_v6( self.server.manager, + self.server.task_manager, product.session_factory, product, self.auth_session, @@ -450,7 +451,7 @@ def do_POST(self): self.send_response(200) self.send_header("content-type", "application/x-thrift") - self.send_header("Content-Length", len(result)) + self.send_header("Content-Length", str(len(result))) self.end_headers() self.wfile.write(result) return @@ -497,182 +498,6 @@ def translate_path(self, path): return path -class Product: - """ - Represents a product, which is a distinct storage of analysis reports in - a separate database (and database connection) with its own access control. - """ - - # The amount of SECONDS that need to pass after the last unsuccessful - # connect() call so the next could be made. - CONNECT_RETRY_TIMEOUT = 300 - - def __init__(self, id_: int, endpoint: str, display_name: str, - connection_string: str, context, check_env): - """ - Set up a new managed product object for the configuration given. - """ - self.__id = id_ - self.__endpoint = endpoint - self.__display_name = display_name - self.__connection_string = connection_string - self.__driver_name = None - self.__context = context - self.__check_env = check_env - self.__engine = None - self.__session = None - self.__db_status = DBStatus.MISSING - - self.__last_connect_attempt = None - - @property - def id(self): - return self.__id - - @property - def endpoint(self): - """ - Returns the accessible URL endpoint of the product. - """ - return self.__endpoint - - @property - def name(self): - """ - Returns the display name of the product. - """ - return self.__display_name - - @property - def session_factory(self): - """ - Returns the session maker on this product's database engine which - can be used to initiate transactional connections. - """ - return self.__session - - @property - def driver_name(self): - """ - Returns the name of the sql driver (sqlite, postgres). - """ - return self.__driver_name - - @property - def db_status(self): - """ - Returns the status of the database which belongs to this product. - Call connect to update it. - """ - return self.__db_status - - @property - def last_connection_failure(self): - """ - Returns the reason behind the last executed connection attempt's - failure. - """ - return self.__last_connect_attempt[1] if self.__last_connect_attempt \ - else None - - def connect(self, init_db=False): - """ - Initiates the actual connection to the database configured for the - product. - - Each time the connect is called the db_status is updated. - """ - LOG.debug("Checking '%s' database.", self.endpoint) - - sql_server = database.SQLServer.from_connection_string( - self.__connection_string, - self.__endpoint, - RUN_META, - self.__context.run_migration_root, - interactive=False, - env=self.__check_env) - - if isinstance(sql_server, database.PostgreSQLServer): - self.__driver_name = 'postgresql' - elif isinstance(sql_server, database.SQLiteDatabase): - self.__driver_name = 'sqlite' - - try: - LOG.debug("Trying to connect to the database") - - # Create the SQLAlchemy engine. - self.__engine = sql_server.create_engine() - LOG.debug(self.__engine) - - self.__session = sessionmaker(bind=self.__engine) - - self.__engine.execute('SELECT 1') - self.__db_status = sql_server.check_schema() - self.__last_connect_attempt = None - - if self.__db_status == DBStatus.SCHEMA_MISSING and init_db: - LOG.debug("Initializing new database schema.") - self.__db_status = sql_server.connect(init_db) - - except Exception as ex: - LOG.exception("The database for product '%s' cannot be" - " connected to.", self.endpoint) - self.__db_status = DBStatus.FAILED_TO_CONNECT - self.__last_connect_attempt = (datetime.datetime.now(), str(ex)) - - def get_details(self): - """ - Get details for a product from the database. - - It may throw different error messages depending on the used SQL driver - adapter in case of connection error. - """ - with DBSession(self.session_factory) as run_db_session: - run_locks = run_db_session.query(RunLock.name) \ - .filter(RunLock.locked_at.isnot(None)) \ - .all() - - runs_in_progress = set(run_lock[0] for run_lock in run_locks) - - num_of_runs = run_db_session.query(Run).count() - - latest_store_to_product = "" - if num_of_runs: - last_updated_run = run_db_session.query(Run) \ - .order_by(Run.date.desc()) \ - .limit(1) \ - .one_or_none() - - latest_store_to_product = last_updated_run.date - - return num_of_runs, runs_in_progress, latest_store_to_product - - def teardown(self): - """ - Disposes the database connection to the product's backend. - """ - if self.__db_status == DBStatus.FAILED_TO_CONNECT: - return - - self.__engine.dispose() - - self.__session = None - self.__engine = None - - def cleanup_run_db(self): - """ - Cleanup the run database which belongs to this product. - """ - LOG.info("[%s] Garbage collection started...", self.endpoint) - - db_cleanup.remove_expired_data(self) - db_cleanup.remove_unused_data(self) - db_cleanup.update_contextual_data(self, self.__context) - - LOG.info("[%s] Garbage collection finished.", self.endpoint) - return True - - def _do_db_cleanup(context, check_env, id_: int, endpoint: str, display_name: str, connection_str: str) -> Tuple[Optional[bool], str]: @@ -782,10 +607,10 @@ def __init__(self, self.manager.set_database_connection(self.config_session) self.__task_queue = task_queue - self.task_manager = BackgroundTaskManager(task_queue, - self.config_session, - server_shutdown_flag, - machine_id) + self.task_manager = BackgroundTaskManager( + task_queue, self.config_session, self.check_env, + server_shutdown_flag, machine_id, + pathlib.Path(self.context.codechecker_workspace)) # Load the initial list of products and set up the server. cfg_sess = self.config_session() @@ -1163,23 +988,6 @@ def start_server(config_directory: str, package_data, port: int, else: LOG.debug("Skipping db_cleanup, as requested.") - def _cleanup_incomplete_tasks(action: str) -> int: - config_session_factory = config_sql_server.create_engine() - try: - return drop_all_incomplete_tasks( - sessionmaker(bind=config_session_factory), - machine_id, action) - finally: - config_session_factory.dispose() - - dropped_tasks = _cleanup_incomplete_tasks( - "New server started with the same machine_id, assuming the old " - "server is dead and won't be able to finish the task.") - if dropped_tasks: - LOG.info("At server startup, dropped %d background tasks left behind " - "by a previous server instance matching machine ID '%s'.", - dropped_tasks, machine_id) - api_processes: Dict[int, Process] = {} requested_api_threads = cast(int, manager.worker_processes) \ or cpu_count() @@ -1194,6 +1002,34 @@ def _cleanup_incomplete_tasks(action: str) -> int: bg_task_queue: Queue = Queue() is_server_shutting_down = Value('B', False) + def _cleanup_incomplete_tasks(action: str) -> int: + config_db = config_sql_server.create_engine() + config_session_factory = sessionmaker(bind=config_db) + tm = BackgroundTaskManager( + bg_task_queue, config_session_factory, check_env, + is_server_shutting_down, machine_id, + pathlib.Path(context.codechecker_workspace)) + + try: + tm.destroy_all_temporary_data() + except OSError: + LOG.warning("Clearing task-temporary storage space failed!") + import traceback + traceback.print_exc() + + try: + return tm.drop_all_incomplete_tasks(action) + finally: + config_db.dispose() + + dropped_tasks = _cleanup_incomplete_tasks( + "New server started with the same machine_id, assuming the old " + "server is dead and won't be able to finish the task.") + if dropped_tasks: + LOG.info("At server startup, dropped %d background tasks left behind " + "by a previous server instance matching machine ID '%s'.", + dropped_tasks, machine_id) + server_clazz = CCSimpleHttpServer if ':' in listen_address: # IPv6 address specified for listening. @@ -1295,6 +1131,7 @@ def spawn_bg_process(): target=background_task_executor, args=(bg_task_queue, config_sql_server, + check_env, is_server_shutting_down, machine_id, ), diff --git a/web/server/codechecker_server/task_executors/abstract_task.py b/web/server/codechecker_server/task_executors/abstract_task.py index f38830ad33..bb665f034e 100644 --- a/web/server/codechecker_server/task_executors/abstract_task.py +++ b/web/server/codechecker_server/task_executors/abstract_task.py @@ -9,6 +9,7 @@ Contains the base class to be inherited and implemented by all background task types. """ +import logging import os import pathlib import shutil @@ -82,6 +83,8 @@ def destroy_data(self): try: shutil.rmtree(self._data_path) + LOG.debug("Wiping temporary data of task '%s' at '%s' ...", + self._token, self._data_path) except Exception as ex: LOG.warning("Failed to remove background task's data_dir at " "'%s':\n%s", self.data_path, str(ex)) @@ -94,7 +97,7 @@ def _implementation(self, _task_manager: "TaskManager") -> None: context of the executed subprocess, to query and mutate service-level information about the current task. """ - raise NotImplementedError() + raise NotImplementedError(f"No implementation for task class {self}!") def execute(self, task_manager: "TaskManager") -> None: """ @@ -183,6 +186,10 @@ def _log_exception_and_fail(db_task: DBTask): db_task.add_comment( f"FAILED!\nException during execution:\n{str(ex)}", "SYSTEM[AbstractTask::execute()]") + if LOG.isEnabledFor(logging.DEBUG): + db_task.add_comment("Debug exception information:\n" + f"{traceback.format_exc()}", + "SYSTEM[AbstractTask::execute()]") db_task.set_finished(successfully=False) task_manager._mutate_task_record(self, _log_exception_and_fail) diff --git a/web/server/codechecker_server/task_executors/main.py b/web/server/codechecker_server/task_executors/main.py index 580a27c4b9..320de737c5 100644 --- a/web/server/codechecker_server/task_executors/main.py +++ b/web/server/codechecker_server/task_executors/main.py @@ -31,6 +31,7 @@ def executor(queue: Queue, config_db_sql_server, + server_environment, server_shutdown_flag: "Value", machine_id: str): """ @@ -66,8 +67,8 @@ def executor_hangup_handler(signum: int, _frame): signal.signal(signal.SIGHUP, executor_hangup_handler) config_db_engine = config_db_sql_server.create_engine() - tm = TaskManager(queue, sessionmaker(bind=config_db_engine), kill_flag, - machine_id) + tm = TaskManager(queue, sessionmaker(bind=config_db_engine), + server_environment, kill_flag, machine_id) while not kill_flag.value: try: diff --git a/web/server/codechecker_server/task_executors/task_manager.py b/web/server/codechecker_server/task_executors/task_manager.py index ddd3b31053..6b6929109a 100644 --- a/web/server/codechecker_server/task_executors/task_manager.py +++ b/web/server/codechecker_server/task_executors/task_manager.py @@ -11,6 +11,8 @@ """ import os from pathlib import Path +import re +import shutil import tempfile from typing import Callable, Optional @@ -24,6 +26,7 @@ from ..database.database import DBSession MAX_TOKEN_RANDOM_RETRIES = 10 +CHARS_INVALID_IN_PATH = re.compile(r"[\'\"<>:\\/\|\*\?\. ]") LOG = get_logger("server") @@ -47,11 +50,33 @@ class TaskManager: """ def __init__(self, q: Queue, config_db_session_factory, - executor_kill_flag: Value, machine_id: str): + server_environment, + executor_kill_flag: Value, + machine_id: str, + temp_dir: Optional[Path] = None): self._queue = q self._database_factory = config_db_session_factory + self._server_environment = server_environment self._is_shutting_down = executor_kill_flag self._machine_id = machine_id + self._temp_dir_root = (temp_dir or Path(tempfile.gettempdir())) \ + / "codechecker_tasks" \ + / CHARS_INVALID_IN_PATH.sub('_', machine_id) + + os.makedirs(self._temp_dir_root, exist_ok=True) + + @property + def configuration_database_session_factory(self): + """ + Returns a `sqlalchemy.orm.sessionmaker` instance for the server + configuration database. + """ + return self._database_factory + + @property + def environment(self): + """Returns the ``check_env`` injected into the task manager.""" + return self._server_environment @property def machine_id(self) -> str: @@ -95,31 +120,78 @@ def allocate_task_record(self, kind: str, summary: str, def create_task_data(self, token: str) -> Path: """ Creates a temporary directory which is **NOT** cleaned up - automatically, and suitable for putting arbitrary files underneath - to communicate large inputs (that should not be put in the `Queue`) - to the `execute` method of an `AbstractTask`. + automatically by the current context, and which is suitable for + putting arbitrary files underneath to communicate large inputs + (that should not be put in the `Queue`) to the `execute` method of + an `AbstractTask`. + + The larger business logic of the Server implementation may still clean + up the temporary directories, e.g., if the pending tasks are being + dropped during a shutdown, making retention of this "temporary data" + useless. + See `destroy_temporary_data`. + """ + task_temp_dir = tempfile.mkdtemp(prefix=f"{token}-", + dir=self._temp_dir_root) + return Path(task_temp_dir) + + def destroy_all_temporary_data(self): """ - task_tmp_root = Path(tempfile.gettempdir()) / "codechecker_tasks" \ - / self.machine_id - os.makedirs(task_tmp_root, exist_ok=True) + Removes the contents of task-temporary directories under the + `TaskManager`'s initial `temp_dir` and current "machine ID". + """ + try: + shutil.rmtree(self._temp_dir_root) + except Exception as ex: + LOG.warning("Failed to remove background tasks' data_dirs at " + "'%s':\n%s", self._temp_dir_root, str(ex)) - task_tmp_dir = tempfile.mkdtemp(prefix=f"{token}-") - return Path(task_tmp_dir) + def drop_all_incomplete_tasks(self, action: str) -> int: + """ + Sets all tasks in the database that were associated with the given + `machine_id` to ``"dropped"`` status, indicating that the status was + changed during the `action`. - def _get_task_record(self, task_obj: "AbstractTask") -> DBTask: + Returns the number of `DBTask`s actually changed. + """ + count: int = 0 + with DBSession(self._database_factory) as session: + for t in session.query(DBTask) \ + .filter(DBTask.machine_id == self.machine_id, + DBTask.status.in_(["allocated", + "enqueued", + "running"])) \ + .all(): + count += 1 + t.add_comment(f"DROPPED!\n{action}", + "SYSTEM") + t.set_abandoned(force_dropped_status=True) + + session.commit() + return count + + def get_task_record(self, token: str) -> DBTask: """ Retrieves the `DBTask` for the task identified by `task_obj`. This class should not be mutated, only the fields queried. """ with DBSession(self._database_factory) as session: - try: - db_task = session.query(DBTask).get(task_obj.token) - session.expunge(db_task) - return db_task - except sqlalchemy.exc.SQLAlchemyError as sql_err: - raise KeyError(f"No task record for token '{task_obj.token}' " - "in the database") from sql_err + db_task: Optional[DBTask] = \ + session.query(DBTask).get(token) + if not db_task: + raise KeyError(f"No task record for token '{token}' " + "in the database") + session.expunge(db_task) + return db_task + + def _get_task_record(self, task_obj: "AbstractTask") -> DBTask: + """ + Retrieves the `DBTask` for the task identified by `task_obj`. + + This class should not be mutated, only the fields queried. + """ + return self.get_task_record(task_obj.token) def _mutate_task_record(self, task_obj: "AbstractTask", mutator: Callable[[DBTask], None]): @@ -128,14 +200,14 @@ def _mutate_task_record(self, task_obj: "AbstractTask", corresponding to the `task_obj` description available in memory. """ with DBSession(self._database_factory) as session: - try: - db_record = session.query(DBTask).get(task_obj.token) - except sqlalchemy.exc.SQLAlchemyError as sql_err: + db_task: Optional[DBTask] = \ + session.query(DBTask).get(task_obj.token) + if not db_task: raise KeyError(f"No task record for token '{task_obj.token}' " - "in the database") from sql_err + "in the database") try: - mutator(db_record) + mutator(db_task) except Exception: session.rollback() @@ -195,35 +267,18 @@ def should_cancel(self, task_obj: "AbstractTask") -> bool: (db_task.status in ["enqueued", "running"] and db_task.cancel_flag) + def add_comment(self, task_obj: "AbstractTask", comment: str, + actor: Optional[str] = None): + """ + Adds `comment` in the name of `actor` to the task record corresponding + to `task_obj`. + """ + self._mutate_task_record(task_obj, + lambda dbt: dbt.add_comment(comment, actor)) + def heartbeat(self, task_obj: "AbstractTask"): """ Triggers ``heartbeat()`` timestamp update in the database for `task_obj`. """ self._mutate_task_record(task_obj, lambda dbt: dbt.heartbeat()) - - -def drop_all_incomplete_tasks(config_db_session_factory, machine_id: str, - action: str) -> int: - """ - Sets all tasks in the database (reachable via `config_db_session_factory`) - that were associated with the given `machine_id` to ``"dropped"`` status, - indicating that the status was changed during the `action`. - - Returns the number of `DBTask`s actually changed. - """ - count: int = 0 - with DBSession(config_db_session_factory) as session: - for t in session.query(DBTask) \ - .filter(DBTask.machine_id == machine_id, - DBTask.status.in_(["allocated", - "enqueued", - "running"])) \ - .all(): - count += 1 - t.add_comment(f"DROPPED!\n{action}", - "SYSTEM") - t.set_abandoned(force_dropped_status=True) - - session.commit() - return count diff --git a/web/server/vue-cli/package-lock.json b/web/server/vue-cli/package-lock.json index d0943fd772..07b56dce13 100644 --- a/web/server/vue-cli/package-lock.json +++ b/web/server/vue-cli/package-lock.json @@ -5115,7 +5115,7 @@ "node_modules/codechecker-api": { "version": "6.59.0", "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz", - "integrity": "sha512-BZCBDRjVFS5UerrXsoPNioQppTfrCdDgToHqfFfaQtk6FPVrER42LchfU+cZl254PgWh58H5bLfqdLyFfqntCg==", + "integrity": "sha512-DN1vQkV3P/5jwI62Sd+JzvNALe/i7km2iDd8GKfK6vQYdYPnHg3ZpwK1vyRcF0dsegZhjfgoMzOAclH+nwk+Yg==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "thrift": "0.13.0-hotfix.1" @@ -21146,7 +21146,7 @@ }, "codechecker-api": { "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.59.0.tgz", - "integrity": "sha512-BZCBDRjVFS5UerrXsoPNioQppTfrCdDgToHqfFfaQtk6FPVrER42LchfU+cZl254PgWh58H5bLfqdLyFfqntCg==", + "integrity": "sha512-DN1vQkV3P/5jwI62Sd+JzvNALe/i7km2iDd8GKfK6vQYdYPnHg3ZpwK1vyRcF0dsegZhjfgoMzOAclH+nwk+Yg==", "requires": { "thrift": "0.13.0-hotfix.1" }