11import logging
2+ from contextlib import suppress
23
34from sqlalchemy .orm import Session
45
1516from src .db import errors as db_errors
1617from src .db .utils import ForUpdateParams
1718from src .handlers .completed_escrows import handle_escrows_validations
19+ from src .handlers .cvat_events import cvat_webhook_handler
1820from src .utils .logging import format_sequence
21+ from src .utils .time import utcnow
22+
23+
24+ @cron_job
25+ def process_incoming_cvat_webhooks (logger : logging .Logger , session : Session ) -> None :
26+ webhooks = cvat_service .incoming_webhooks .get_pending_webhooks (
27+ session = session ,
28+ limit = CronConfig .process_cvat_webhooks_chunk_size ,
29+ for_update = ForUpdateParams (skip_locked = True ),
30+ )
31+
32+ for webhook in webhooks :
33+ try :
34+ with session .begin_nested ():
35+ cvat_webhook_handler (webhook , session )
36+ cvat_service .incoming_webhooks .handle_webhook_success (
37+ session , webhook_id = webhook .id
38+ )
39+ except Exception as e :
40+ with session .begin_nested ():
41+ logger .exception (e )
42+ cvat_service .incoming_webhooks .handle_webhook_fail (session , webhook_id = webhook .id )
1943
2044
2145@cron_job
@@ -53,18 +77,51 @@ def track_assignments(logger: logging.Logger) -> None:
5377 4. If a project or task state is not "annotation", cancels assignments
5478 """
5579
80+ def _try_complete_assignment (
81+ session : Session ,
82+ assignment : cvat_models .Assignment ,
83+ ) -> bool :
84+ """
85+ Checks if we haven't received a notification, but the job might have been completed.
86+
87+ Returns: assignment completed
88+ """
89+
90+ latest_assignment = cvat_service .get_latest_assignment_by_cvat_job_id (
91+ session , assignment .cvat_job_id
92+ )
93+ if not latest_assignment or latest_assignment .id != assignment .id :
94+ return False
95+
96+ try :
97+ cvat_job = cvat_api .get_job (assignment .cvat_job_id )
98+ except cvat_api .exceptions .NotFoundException :
99+ return False
100+
101+ if cvat_job .state != cvat_api .JobStatus .completed :
102+ return False
103+
104+ if not cvat_job .assignee or cvat_job .assignee .id != latest_assignment .user .cvat_id :
105+ return False
106+
107+ logger .info (f"Found completed job #{ assignment .cvat_job_id } . Completing the assignment" )
108+ cvat_service .complete_assignment (session , assignment .id , completed_at = utcnow ())
109+ cvat_api .update_job_assignee (assignment .cvat_job_id , assignee_id = None )
110+ cvat_service .update_job_status (session , assignment .job .id , status = JobStatuses .completed )
111+ cvat_service .touch (session , cvat_models .Job , [assignment .job .id ])
112+ return True
113+
56114 def _reset_job_after_assignment (session : Session , assignment : cvat_models .Assignment ):
57115 latest_assignment = cvat_service .get_latest_assignment_by_cvat_job_id (
58116 session , assignment .cvat_job_id
59117 )
60- if latest_assignment .id = = assignment .id :
118+ if latest_assignment .id ! = assignment .id :
61119 # Avoid un-assigning if it's not the latest assignment
120+ return
62121
63- cvat_api .update_job_assignee (
64- assignment .cvat_job_id , assignee_id = None
65- ) # note that calling it in a loop can take too much time
66-
67- cvat_service .update_job_status (session , assignment .job .id , status = JobStatuses .new )
122+ cvat_api .update_job_assignee (assignment .cvat_job_id , assignee_id = None )
123+ cvat_service .update_job_status (session , assignment .job .id , status = JobStatuses .new )
124+ cvat_service .touch (session , cvat_models .Job , [assignment .job .id ])
68125
69126 with SessionLocal .begin () as session :
70127 assignments = cvat_service .get_unprocessed_expired_assignments (
@@ -74,18 +131,27 @@ def _reset_job_after_assignment(session: Session, assignment: cvat_models.Assign
74131 )
75132
76133 for assignment in assignments :
77- logger .info (
78- "Expiring the unfinished assignment {} (user {}, job id {})" .format (
79- assignment .id ,
80- assignment .user_wallet_address ,
81- assignment .cvat_job_id ,
134+ with (
135+ session .begin_nested (),
136+ suppress (db_errors .LockNotAvailable ),
137+ ):
138+ cvat_service .get_jobs_by_cvat_id (
139+ session ,
140+ cvat_ids = [assignment .cvat_job_id ],
141+ for_update = ForUpdateParams (nowait = True ),
82142 )
83- )
84143
85- cvat_service .expire_assignment (session , assignment .id )
86- _reset_job_after_assignment (session , assignment )
144+ if not _try_complete_assignment (session , assignment ):
145+ logger .info (
146+ "Expiring the unfinished assignment {} (user {}, job id {})" .format (
147+ assignment .id ,
148+ assignment .user_wallet_address ,
149+ assignment .cvat_job_id ,
150+ )
151+ )
87152
88- cvat_service .touch (session , cvat_models .Job , [a .job .id for a in assignments ])
153+ cvat_service .expire_assignment (session , assignment .id )
154+ _reset_job_after_assignment (session , assignment )
89155
90156 with SessionLocal .begin () as session :
91157 assignments = cvat_service .get_unprocessed_cancelled_assignments (
@@ -95,16 +161,24 @@ def _reset_job_after_assignment(session: Session, assignment: cvat_models.Assign
95161 )
96162
97163 for assignment in assignments :
98- logger .info (
99- "Finalizing the canceled assignment {} (user {}, job id {})" .format (
100- assignment .id ,
101- assignment .user_wallet_address ,
102- assignment .cvat_job_id ,
164+ with (
165+ session .begin_nested (),
166+ suppress (db_errors .LockNotAvailable ),
167+ ):
168+ cvat_service .get_jobs_by_cvat_id (
169+ session ,
170+ cvat_ids = [assignment .cvat_job_id ],
171+ for_update = ForUpdateParams (nowait = True ),
103172 )
104- )
105- _reset_job_after_assignment (session , assignment )
106173
107- cvat_service .touch (session , cvat_models .Job , [a .job .id for a in assignments ])
174+ logger .info (
175+ "Finalizing the canceled assignment {} (user {}, job id {})" .format (
176+ assignment .id ,
177+ assignment .user_wallet_address ,
178+ assignment .cvat_job_id ,
179+ )
180+ )
181+ _reset_job_after_assignment (session , assignment )
108182
109183 with SessionLocal .begin () as session :
110184 assignments = cvat_service .get_active_assignments (
@@ -115,19 +189,27 @@ def _reset_job_after_assignment(session: Session, assignment: cvat_models.Assign
115189
116190 for assignment in assignments :
117191 if assignment .job .project .status != ProjectStatuses .annotation :
118- logger .warning (
119- "Canceling the unfinished assignment {} (user {}, job id {}) - "
120- "the project state is not annotation" .format (
121- assignment .id ,
122- assignment .user_wallet_address ,
123- assignment .cvat_job_id ,
192+ with (
193+ session .begin_nested (),
194+ suppress (db_errors .LockNotAvailable ),
195+ ):
196+ cvat_service .get_jobs_by_cvat_id (
197+ session ,
198+ cvat_ids = [assignment .cvat_job_id ],
199+ for_update = ForUpdateParams (nowait = True ),
124200 )
125- )
126201
127- cvat_service .cancel_assignment (session , assignment .id )
128- _reset_job_after_assignment (session , assignment )
202+ logger .warning (
203+ "Canceling the unfinished assignment {} (user {}, job id {}) - "
204+ "the project state is not annotation" .format (
205+ assignment .id ,
206+ assignment .user_wallet_address ,
207+ assignment .cvat_job_id ,
208+ )
209+ )
129210
130- cvat_service .touch (session , cvat_models .Job , [a .job .id for a in assignments ])
211+ cvat_service .cancel_assignment (session , assignment .id )
212+ _reset_job_after_assignment (session , assignment )
131213
132214
133215@cron_job
0 commit comments