[Fable 5][P2] scrape_kn: Redis-lock без owner-токена — release удаляет чужой lock, а stale lock после рестарта молча убивает resume #1216

Closed
opened 2026-06-13 04:49:45 +00:00 by bot-backend · 0 comments
Collaborator

Из армейского ревью Claude Fable 5 (2026-06-11)

Confirmed by 2/2 адверсариальных верификатора, severity P2 (concurrency), confidence 0.65.

Файл

backend/app/workers/tasks/scrape_kn.py:110

Evidence (цитата кода)

acquired = r.set(key, "1", nx=True, ex=_LOCK_TTL_SECONDS)
try:
    yield bool(acquired)
finally:
    if acquired:
        try:
            r.delete(key)

Why bug

TTL=30 мин при заявленной длительности sweep'а ~25-30 мин (комментарий строки 21) — нулевой запас. Sweep дольше TTL → lock истекает, второй sweep стартует параллельно; первый при завершении безусловным r.delete сносит ЧУЖОЙ lock → возможен третий параллельный. Обратный кейс: SIGKILL/redeploy mid-sweep — finally не выполняется, lock живёт до 30 мин; worker_ready сразу метит run 'zombie' и enqueue'ит resume_kn_run, который ловит lock_held и возвращает skipped НАВСЕГДА (повторного enqueue нет) → недокачанный run теряется до следующего недельного beat.

Suggested fix

Класть в lock уникальный token (uuid) и удалять через Lua check-and-delete только свой; resume_kn_run при lock_held — self.retry с countdown вместо терминального skip.

Verify notes

  • [real/P2] Трасса подтверждена. (1) SIGKILL/redeploy mid-sweep: finally не выполняется, lock живёт до 30 мин; worker_ready метит run 'zombie' и сразу (apply_async без countdown) шлёт resume_kn_run → SET NX fail → skipped без retry (max_retries инертен, self.retry нет); zombie больше никем не подхватывается (сканы ищут только 'running', nspd-cleanup не про kn) → resume мёртв до недельного beat. (2) Value="1" без owner-токена, TTL=30мин при sweep 25-30мин без renewal → r.delete сносит чужой lock.
  • [real/P2] Защиты нет: delete без owner-токена; TTL 30 мин = заявленной длительности sweep («25-30 мин, 30 — потолок»), refresh TTL в domrf_kn нет. Главное: при kill mid-sweep (grace 10s, Redis отдельный сервис с volume — lock переживает redeploy) worker_ready сразу метит run 'zombie' и enqueue'ит resume без countdown; resume ловит lock_held и возвращает dict без raise — retry нет. Re-enqueue невозможен: worker_ready сканирует только 'running', /queue лишь метит zombie, beat-cleanup — только nspd_geo.

Provenance

  • Finders: workers
  • Confidence: 0.65
  • Originally batched at: main@d796027
## Из армейского ревью Claude Fable 5 (2026-06-11) Confirmed by 2/2 адверсариальных верификатора, severity **P2** (concurrency), confidence 0.65. ## Файл [`backend/app/workers/tasks/scrape_kn.py:110`](backend/app/workers/tasks/scrape_kn.py:110) ## Evidence (цитата кода) ``` acquired = r.set(key, "1", nx=True, ex=_LOCK_TTL_SECONDS) try: yield bool(acquired) finally: if acquired: try: r.delete(key) ``` ## Why bug TTL=30 мин при заявленной длительности sweep'а ~25-30 мин (комментарий строки 21) — нулевой запас. Sweep дольше TTL → lock истекает, второй sweep стартует параллельно; первый при завершении безусловным r.delete сносит ЧУЖОЙ lock → возможен третий параллельный. Обратный кейс: SIGKILL/redeploy mid-sweep — finally не выполняется, lock живёт до 30 мин; worker_ready сразу метит run 'zombie' и enqueue'ит resume_kn_run, который ловит lock_held и возвращает skipped НАВСЕГДА (повторного enqueue нет) → недокачанный run теряется до следующего недельного beat. ## Suggested fix Класть в lock уникальный token (uuid) и удалять через Lua check-and-delete только свой; resume_kn_run при lock_held — self.retry с countdown вместо терминального skip. ## Verify notes - [real/P2] Трасса подтверждена. (1) SIGKILL/redeploy mid-sweep: finally не выполняется, lock живёт до 30 мин; worker_ready метит run 'zombie' и сразу (apply_async без countdown) шлёт resume_kn_run → SET NX fail → skipped без retry (max_retries инертен, self.retry нет); zombie больше никем не подхватывается (сканы ищут только 'running', nspd-cleanup не про kn) → resume мёртв до недельного beat. (2) Value="1" без owner-токена, TTL=30мин при sweep 25-30мин без renewal → r.delete сносит чужой lock. - [real/P2] Защиты нет: delete без owner-токена; TTL 30 мин = заявленной длительности sweep («25-30 мин, 30 — потолок»), refresh TTL в domrf_kn нет. Главное: при kill mid-sweep (grace 10s, Redis отдельный сервис с volume — lock переживает redeploy) worker_ready сразу метит run 'zombie' и enqueue'ит resume без countdown; resume ловит lock_held и возвращает dict без raise — retry нет. Re-enqueue невозможен: worker_ready сканирует только 'running', /queue лишь метит zombie, beat-cleanup — только nspd_geo. ## Provenance - Finders: `workers` - Confidence: 0.65 - Originally batched at: main@d796027
bot-backend added the
Fable 5 ревью
label 2026-06-13 04:49:45 +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#1216
No description provided.