fix(ptica): упавший прогон Объектива помечается failed, а не висит running вечно (#2464) #2972
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2972
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2464-objective-run-stuck"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Пункт эпика #2464:
scrape_objective.py:252.Дефект
Если исходный сбой был DB-level (напр. INSERT в
_save_raw), транзакция остаётся в aborted-состоянии:_finish_runпадает уже на своёмexecute, отказ гасится голымpass, и строка прогона навсегда остаётся вstatus='running'.Замер прода 20.08
Уборщика зомби для
objective_scrape_runsнет — в отличие от cadastre, где он есть (cadastre-zombie-cleanupв расписании). Поэтому строки и висят с мая.Правка
rollbackперед_finish_run. Сессия здесь своя (SessionLocal()в этой же функции,closeвfinally), поэтому плоский rollback законен: он отбрасывает уже провалившуюся транзакцию и ничего чужого не теряет.Плюс отказ самого
_finish_runбольше не молчит: если и после rollback не прошло — это логируется. Знать об этом важнее, чем сохранить тишину в логе; именно тишина и держала шесть строк незамеченными 95 суток.Тест
На
PostgresLikeSession— двойнике с настоящей семантикой aborted-транзакции. НаMagicMockбыл бы зелёным по построению.Против
origin/main:Второй контроль не для симметрии: ловит «починку», которая заодно погасила бы исключение — тогда Celery считал бы упавший прогон успешным.
Чего не делаю
Шесть уже висящих строк не трогаю. Правка предотвращает новые, а чистка старых — отдельное решение по данным прода. На них ничего не завязано: с 17.05 прошёл 71 успешный прогон.
Прогоны
Обработчик выглядел так: except Exception as e: if run_id: try: _finish_run(db, run_id, status="failed", ...) except Exception: pass # ← молча Если исходный сбой был DB-level (напр. INSERT в _save_raw), транзакция остаётся в aborted-состоянии: _finish_run падает уже на своём execute, отказ гасится голым pass, и строка прогона навсегда остаётся в status='running'. Замер прода 20.08: шесть таких строк висят с 17.05 — 2274 часа, 95 суток. Уборщика зомби для objective_scrape_runs нет (в отличие от cadastre, где он есть). Правка: rollback перед _finish_run. Сессия здесь СВОЯ (SessionLocal() в этой же функции, close в finally), поэтому плоский rollback законен — он отбрасывает уже провалившуюся транзакцию и ничего чужого не теряет. Плюс отказ самого _finish_run больше не молчит: если и после rollback не прошло, это логируется — знать об этом важнее, чем сохранить тишину. Тест на PostgresLikeSession — двойнике с настоящей семантикой aborted-транзакции. На MagicMock он был бы зелёным по построению. Против origin/main: UPDATE статуса не выполнился, журнал SQL пуст → падает сессия закрывается в finally — контроль, зелёный с обеих сторон исходная ошибка пробрасывается — контроль, зелёный с обеих сторон Второй контроль не для симметрии: ловит «починку», которая заодно погасила бы исключение — тогда Celery считал бы упавший прогон успешным. Шесть уже висящих строк не трогаю: правка предотвращает новые, а чистка старых — отдельное решение (данные прода, и на них ничего не завязано: с 17.05 прошёл 71 успешный прогон). Прогоны: tests/workers — 226 passed rc=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>