fix: гонка dispatch-before-commit (search/plagiarism) + detached user в notify
All checks were successful
Deploy / deploy (push) Successful in 23s

Три бага, найденные при аудите прода:
- search и documents диспатчили Celery-задачу ДО commit() — быстрый воркер
  читал задачу раньше, чем транзакция закоммичена, и падал «задача не найдена»
  (поиск не работал вовсе; плагиат спасала латентность скачивания из MinIO).
  Теперь коммитим до диспатча.
- notify.send_task_done обращался к user.email/name и task.type ПОСЛЕ закрытия
  сессии → DetachedInstanceError, письма о завершении уходили в ретраи.
  Значения достаются внутри сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-07-30 13:04:49 +05:00
parent 8dc51a4f65
commit 61b2275972
3 changed files with 22 additions and 15 deletions

View File

@@ -130,8 +130,13 @@ async def upload_for_plagiarism_check(
"file_size_bytes": len(file_data),
},
)
task.queue_position = 1
task.eta_seconds = 120
db.add(task)
await db.flush()
# Коммитим ДО диспатча — иначе воркер может прочитать задачу раньше коммита
# (гонка dispatch-before-commit).
await db.commit()
await db.refresh(task)
celery_result = celery_app.send_task(
"index.extract_and_check",
@@ -140,11 +145,7 @@ async def upload_for_plagiarism_check(
)
task.celery_task_id = celery_result.id
task.queue_position = 1
task.eta_seconds = 120
await db.commit()
await db.refresh(task)
logger.info("Задача плагиата %s создана для пользователя %d", task.public_id, current_user.id)
return TaskResponse.model_validate(task)

View File

@@ -66,8 +66,13 @@ async def create_search_task(
"category": data.category,
},
)
task.queue_position = 1
task.eta_seconds = ETA_PER_POSITION_SECONDS
db.add(task)
await db.flush() # получаем id и public_id
# Коммитим ДО диспатча: иначе быстрый воркер прочитает задачу раньше, чем
# транзакция закоммичена, и не найдёт её в БД (гонка dispatch-before-commit).
await db.commit()
await db.refresh(task)
celery_result = celery_app.send_task(
"gpu.search_semantic",
@@ -77,11 +82,7 @@ async def create_search_task(
)
task.celery_task_id = celery_result.id
task.queue_position = 1
task.eta_seconds = ETA_PER_POSITION_SECONDS
await db.commit()
await db.refresh(task)
logger.info("Задача поиска %s создана для пользователя %d", task.public_id, current_user.id)
return TaskResponse.model_validate(task)