nspd_geo: jobs, застрявшие в 'queued' (потерянное брокер-сообщение), никогда не ре-reap'ятся — cleanup_zombies и worker_ready матчат только 'running'/'paused' #1655

Closed
opened 2026-06-16 18:47:06 +00:00 by bot-backend · 0 comments
Collaborator

Severity: low · Категория: concurrency/zombie-reaping · Файл: backend/app/workers/tasks/nspd_geo.py:651-664, 677-682

Файлы: backend/app/workers/tasks/nspd_geo.py:644-693 (cleanup_zombies), 651-664 (UPDATE/WHERE), 677-682 (re-enqueue); backend/app/workers/lifecycle.py:154-189, 164 (worker_ready WHERE).

Суть: cleanup_zombies переводит zombie в status='queued' (652) и ре-enqueue'ит process_nspd_geo_job (679), но в UPDATE SET НЕ обновляет heartbeat_at (только status + error). Собственный WHERE (654-663) матчит лишь status='running' (>6мин) и status='paused' (>30мин). worker_ready (lifecycle.py:164) тоже только 'running'/'paused'.

Никакой периодический task или boot-hook не матчит status='queued' (подтверждено грепом: 'queued' ставится только в worker_ready, cleanup_zombies и при создании job nspd_geo.py:323). Если ре-enqueue'нутое Celery-сообщение теряется (flush брокера, crash воркера до pickup, purge очереди), job навсегда висит в 'queued' со stale heartbeat_at: в 'running' его переводит только _start_job (82), а он отрабатывает лишь при фактическом pickup таски.

Импакт: редкий, но перманентный stranding job'а в 'queued' без авто-восстановления; требует ручного вмешательства (повторный enqueue через admin). Не покрыто #1215 (расширение окна cleanup) и #1235 (SoftTimeLimit).

Фикс: добавить в WHERE cleanup_zombies третью ветку — status='queued' AND heartbeat_at < NOW() - CAST(:queued_threshold AS interval) (порог напр. 10мин); при этом cleanup_zombies должен обновлять heartbeat_at=NOW() при переводе в 'queued' (или вести отдельный enqueued_at), чтобы ре-reap происходил по времени и не зацикливался каждую минуту. Альтернатива: использовать persistent broker-подтверждение / outbox для enqueue.

Почему баг: The reaping state machine has no recovery path for the 'queued' state, yet cleanup_zombies and worker_ready both deliberately leave jobs in 'queued' as the resume signal. A dropped broker message strands the job permanently.

Как чинить: Include OR (status='queued' AND heartbeat_at < NOW() - interval '<threshold>') in the cleanup_zombies WHERE clause, and have cleanup_zombies/worker_ready bump heartbeat_at (or queued_at) when transitioning to 'queued' so the staleness clock restarts and stuck 'queued' jobs get re-enqueued.


deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.7 · unit B43

**Severity:** low · **Категория:** concurrency/zombie-reaping · **Файл:** `backend/app/workers/tasks/nspd_geo.py:651-664, 677-682` Файлы: backend/app/workers/tasks/nspd_geo.py:644-693 (cleanup_zombies), 651-664 (UPDATE/WHERE), 677-682 (re-enqueue); backend/app/workers/lifecycle.py:154-189, 164 (worker_ready WHERE). Суть: cleanup_zombies переводит zombie в status='queued' (652) и ре-enqueue'ит process_nspd_geo_job (679), но в UPDATE SET НЕ обновляет heartbeat_at (только status + error). Собственный WHERE (654-663) матчит лишь status='running' (>6мин) и status='paused' (>30мин). worker_ready (lifecycle.py:164) тоже только 'running'/'paused'. Никакой периодический task или boot-hook не матчит status='queued' (подтверждено грепом: 'queued' ставится только в worker_ready, cleanup_zombies и при создании job nspd_geo.py:323). Если ре-enqueue'нутое Celery-сообщение теряется (flush брокера, crash воркера до pickup, purge очереди), job навсегда висит в 'queued' со stale heartbeat_at: в 'running' его переводит только _start_job (82), а он отрабатывает лишь при фактическом pickup таски. Импакт: редкий, но перманентный stranding job'а в 'queued' без авто-восстановления; требует ручного вмешательства (повторный enqueue через admin). Не покрыто #1215 (расширение окна cleanup) и #1235 (SoftTimeLimit). Фикс: добавить в WHERE cleanup_zombies третью ветку — `status='queued' AND heartbeat_at < NOW() - CAST(:queued_threshold AS interval)` (порог напр. 10мин); при этом cleanup_zombies должен обновлять heartbeat_at=NOW() при переводе в 'queued' (или вести отдельный enqueued_at), чтобы ре-reap происходил по времени и не зацикливался каждую минуту. Альтернатива: использовать persistent broker-подтверждение / outbox для enqueue. **Почему баг:** The reaping state machine has no recovery path for the 'queued' state, yet cleanup_zombies and worker_ready both deliberately leave jobs in 'queued' as the resume signal. A dropped broker message strands the job permanently. **Как чинить:** Include `OR (status='queued' AND heartbeat_at < NOW() - interval '<threshold>')` in the cleanup_zombies WHERE clause, and have cleanup_zombies/worker_ready bump heartbeat_at (or queued_at) when transitioning to 'queued' so the staleness clock restarts and stuck 'queued' jobs get re-enqueued. --- <sub>deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.7 · unit B43</sub>
bot-backend added the
week ревью 1
label 2026-06-16 18:47:06 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1655
No description provided.