@@ -316,6 +316,84 @@ async def get_experiment_stats_optimized(experiment_id: str, db) -> Dict:
316316 }
317317
318318
319+ async def check_and_update_group_status (experiment_id : str , db ):
320+ """Проверяет и обновляет статус группы экспериментов после завершения эксперимента.
321+
322+ Находит все группы, содержащие завершившийся эксперимент, и проверяет,
323+ завершились ли все эксперименты в группе. Если да, обновляет статус группы на COMPLETED.
324+ """
325+ try :
326+ # Находим все группы, содержащие этот эксперимент
327+ # В MongoDB запрос {"experiment_ids": value} находит документы, где value содержится в массиве
328+ groups = await db .groups .find ({
329+ "experiment_ids" : experiment_id ,
330+ "state" : ExperimentState .RUNNING # Проверяем только запущенные группы
331+ }).to_list (None )
332+
333+ if not groups :
334+ logger .debug (f"No running groups found for experiment { experiment_id } " )
335+ return
336+
337+ logger .info (f"Found { len (groups )} running group(s) containing experiment { experiment_id } " )
338+
339+ for group in groups :
340+ group_id = str (group ["_id" ])
341+ experiment_ids = group .get ("experiment_ids" , [])
342+
343+ if not experiment_ids :
344+ logger .warning (f"Group { group_id } has no experiment_ids" )
345+ continue
346+
347+ logger .debug (f"Checking group { group_id } with { len (experiment_ids )} experiments" )
348+
349+ # Проверяем статусы всех экспериментов в группе
350+ all_finished = True
351+
352+ for exp_id in experiment_ids :
353+ try :
354+ if not ObjectId .is_valid (exp_id ):
355+ logger .warning (f"Invalid experiment ID format in group { group_id } : { exp_id } " )
356+ all_finished = False
357+ break
358+
359+ experiment = await db .experiments .find_one ({"_id" : ObjectId (exp_id )})
360+ if not experiment :
361+ logger .warning (f"Experiment { exp_id } not found in database for group { group_id } " )
362+ all_finished = False
363+ break
364+
365+ exp_state = experiment .get ("state" )
366+
367+ # Если хотя бы один эксперимент еще выполняется или приостановлен, группа не завершена
368+ if exp_state in [ExperimentState .RUNNING , ExperimentState .PENDING , ExperimentState .PAUSED ]:
369+ all_finished = False
370+ logger .debug (f"Group { group_id } not finished: experiment { exp_id } is { exp_state } " )
371+ break
372+
373+ except Exception as exp_error :
374+ logger .error (f"Error checking experiment { exp_id } in group { group_id } : { str (exp_error )} " )
375+ all_finished = False
376+ break
377+
378+ # Если все эксперименты завершены (completed или failed), обновляем статус группы
379+ if all_finished :
380+ try :
381+ await with_retry (
382+ db .groups .update_one ,
383+ {"_id" : ObjectId (group_id )},
384+ {"$set" : {"state" : ExperimentState .COMPLETED , "updated_at" : datetime .now ()}}
385+ )
386+ logger .info (f"Group { group_id } status updated to COMPLETED - all experiments finished" )
387+ except Exception as update_error :
388+ logger .error (f"Failed to update group { group_id } status: { str (update_error )} " )
389+ else :
390+ logger .debug (f"Group { group_id } not yet completed - some experiments still running" )
391+
392+ except Exception as e :
393+ logger .error (f"Error checking group status for experiment { experiment_id } : { str (e )} " , exc_info = True )
394+ # Не прерываем выполнение, если проверка группы не удалась
395+
396+
319397async def run_experiment (experiment_id : str ):
320398 """Фоновая задача для выполнения эксперимента с динамической нагрузкой."""
321399 if experiment_id in active_experiments :
@@ -325,6 +403,7 @@ async def run_experiment(experiment_id: str):
325403 active_experiments .add (experiment_id )
326404 semaphore = asyncio .Semaphore (MAX_CONCURRENT_TASKS )
327405 save_semaphore = asyncio .Semaphore (1 ) # Семафор для сохранения результатов
406+ db = None # Инициализируем для доступа в finally блоке
328407
329408 try :
330409 db = await get_database ()
@@ -510,6 +589,15 @@ async def run_experiment(experiment_id: str):
510589 except Exception as update_error :
511590 logger .error (f"Failed to update experiment state: { str (update_error )} " )
512591 finally :
592+ # Проверяем и обновляем статус группы экспериментов после завершения эксперимента
593+ # Это выполнится независимо от того, как завершился эксперимент (успешно, с ошибкой или отменен)
594+ if db is not None :
595+ try :
596+ await check_and_update_group_status (experiment_id , db )
597+ except Exception as group_check_error :
598+ # Логируем ошибку, но не прерываем выполнение
599+ logger .error (f"Failed to check group status in finally block: { str (group_check_error )} " )
600+
513601 active_experiments .remove (experiment_id )
514602
515603
0 commit comments