fix(scheduler): atomic _claim_run via advisory xact lock (Refs #750) #752
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#752
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/750-scheduler-atomic-claim"
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?
Что
Закрывает TOCTOU в
scheduler._claim_run: конкурентные тики одногоsource(redeploy-overlap старого+нового контейнера, или 2 реплики воркера) оба проходилиhas_running_runpre-check до того как любой из них успевал создать running-run → запускался двойной city-sweep → 2× rate к Avito/Cian → бан скрапера.Как (Path A — advisory xact lock)
После pre-check берём
pg_try_advisory_xact_lock(hashtext(:source)):return None(тик просто пропускается, следующий возьмёт);has_running_run(running мог закоммититься в окне между pre-check и взятием лока);commit/rollback, отдельного release не нужно.Почему не Path B (partial unique index)
Спека рекомендовала
CREATE UNIQUE INDEX ... WHERE status='running'. Отклонил: миграция упадёт на deploy, если в таблице уже есть ≥2 running-строк на один source — а это ровно то состояние, которое текущий баг и создаёт. Рискованно прямо перед Praktika-demo (Mon 06-01). Advisory lock не трогает схему и деградирует безопасно.Тесты (
tests/test_scheduler.py, offline, DB-mock)test_claim_run_double_call_creates_one_run— 2 последовательных claim →create_runровно 1×, второйNone.test_claim_run_skips_when_advisory_lock_unavailable— лок занят →None,create_runне вызван.test_claim_run_rechecks_running_under_lock— running появился под локом →None+rollback(release).pytest -k scheduler→ 9 passed.ruff checkclean (заодно автофикс 5 pre-existing UP017timezone.utc→UTCв этом же файле).Refs #750
✅ APPROVE
Atomic
_claim_runчерез advisory xact lock (#750). Issue предлагал ОБА пути как приемлемые (рекомендовал B, разрешил A). Worker выбрал A с точным обоснованием, с которым согласен: Path BCREATE UNIQUE INDEX … WHERE status='running'упадёт на deploy если уже есть ≥2 running-строк на source — ровно то состояние, что создаёт этот баг — сломав деплой накануне Praktika. Path A не трогает схему и деградирует безопасно. Здравое суждение в рамках разрешённых опций.Correctness (advisory-lock паттерн)
pg_try_advisory_xact_lock(hashtext(:source))— non-blocking, transaction-scoped, single-arg bigint форма валидна; конкурентный тик не берёт →None.has_running_runловит run, закоммиченный между pre-check и взятием лока →rollback(release) +None. Остаточное окно закрыто корректно.Tests — 3 новых со stateful fake (double-call→one run, lock-unavailable→None, running-under-lock→None+rollback) + признанный ruff UP017 автофикс (
timezone.utc→UTC, механический). 9 passed, ruff clean.Scope/security — scheduler.py + тест; нет миграции, нет secrets, нет self-extending триггера.
Advisory —
hashtextint4-коллизия может изредка сериализовать два несвязанных source — безвредно (пропущенный тик, ретрай в след. цикле). Live redeploy-overlap → qa.