Что произошло
В четверг 14 мая в 18:00 MSK мониторинг подавился на kad.arbitr и замолчал на 2 дня 20 часов — до утра воскресенья 17 мая, когда ты прислал «pipeline has error, watch logs».
Снаружи это выглядело так:
- Telegram-уведомления о новых определениях перестали приходить
- В Telegram прилетел single alert «pipeline error»
- Дашборд работал, дискавери новых дел в watching тоже работал — то есть «половина системы» жила
- А именно проверка определений на kad.arbitr — стояла
Подтянулось всё, как только мы пнули руками: 645 дел в очереди, первое уведомление за 64 часа простоя — А73-6533/2026 «АГВУС» (ЦФОП в определении от 15 мая) — ушло через 1 минуту после починки. Остальной backlog ушёл в течение нескольких часов.
Что сломалось технически
Цепочка такая:
-
14 мая 18:00 MSK. kad.arbitr перестал отдавать страницы карточек — каждый запрос упирался в 90-секундный таймаут (антибот pravo.tech включил защиту на наш прокси-IP). Это нормальное состояние — раз в недели-две прокси сжигается, и мы переключаемся на новый.
-
Python поймал фатальную ошибку и пошёл закрывать браузер. Это штатная процедура — на любой fatal сценарий мы корректно гасим Chromium, освобождаем прокси, пишем статистику в базу.
-
Закрытие зависло. Браузер был в «отравленном» состоянии после антибот-блока. Команда «закрой страницу» уходила в Chromium через цепочку pipe → Node.js → CDP → Chrome, и где-то по дороге пайп оборвался без ответа. Python-вызов
browser.close()тихо завис в ожидании ответа, который не придёт никогда. -
Bash-обёртка ждала Python. Python жив → не завершился → bash жив → лок-файл
/tmp/bankruptcy-monitor.lockостался лежать. -
Каждый следующий cron-тик (каждые 15 минут) видел лок-файл, проверял жив ли держатель — да, жив! — и тихо выходил. Никаких ошибок, никаких алертов. Просто
Monitor already running (PID 2936547), skipping. 192 раза подряд.
Итог: 2 дня 20 часов мониторинг был «как бы запущен», но реально ничего не делал. RAM съело 4.6 GB на зомби-процессах Chromium. Никаких признаков снаружи кроме отсутствия Telegram.
Что мы сделали
Защиту построили в два независимых слоя — если один пропустит, второй поймает.
Слой 1 — bash-обвязка (PR #276)
В scripts/cron_monitor.sh добавилась проверка возраста лок-файла. Логика:
- Лок есть и держатель жив → проверь сколько лок-файлу часов
- Меньше 2 часов — нормальная ситуация, штатная работа, выходим тихо
- Больше 2 часов — это аномалия, никакой штатный запуск столько не длится. Принудительно убиваем держателя со всем поддеревом процессов, чистим осиротевшие Xvfb-дисплеи старше 2 часов, удаляем лок, продолжаем нормально
Это первая страховка. Даже если Python завис «насмерть», cron на следующем тике после 2-часовой отметки сам себя починит — без вмешательства руками.
Слой 2 — Python-клиент браузера (PR #279)
В src/scraper/client.py каждый вызов «закрой» теперь обёрнут жёстким таймаутом:
page.close()— 10 секундcontext.close()— 10 секундbrowser.close()— 15 секундplaywright.stop()— 15 секунд
Если Chromium не отвечает за это время — Python не ждёт бесконечно, а пишет warning в лог и идёт дальше. Процесс завершается штатно → лок-файл снимается → cron на следующем тике запускается чисто.
В худшем сценарии (Chromium совсем не отвечает): Python теряет до 15 секунд на каждый из 4 шагов закрытия = максимум минута. Это не «зависание на 2 дня».
Что покрыто двумя слоями
| Сценарий | Слой 1 (bash) | Слой 2 (Python) |
|---|---|---|
| Штатный fatal с быстрым закрытием | не активируется | не активируется |
| Закрытие зависло на pravo.tech-блоке | сработает через 2 ч | сработает через ≤1 мин |
| Python завис вне процедуры закрытия (бесконечный цикл, deadlock) | сработает через 2 ч | не сработает |
| Bash-обвязка сломалась (порча скрипта, сбой xvfb-run) | не сработает | поможет если успеет |
Любая комбинация — максимум 2 часа простоя вместо «пока ты не напишешь "watch logs"».
Что ты увидишь
В нормальной работе — ничего. Это защита, она не должна срабатывать на штатных запусках.
Если в логах когда-то появятся строки вида:
WARN: monitor PID 12345 alive 7250s > 7200s — force-killing hung tree
или
browser.close() timed out / errored: ...
— значит защита сработала. Один из двух слоёв поймал зависание, система продолжает работать. Можно ничего не делать — для информации.
Конкретно сейчас:
- Backlog за 14–17 мая полностью обработан, все пропущенные определения подтянуты
- Telegram-уведомления приходят как обычно
- 16 открытых задач в проектной доске (после очистки от 41) — продолжаем по плану
Что дальше
- Наблюдаем 7 дней. Если за неделю не было ни одного срабатывания force-kill — значит штатно (хорошо: бан-окно у нас редкое). Если было 1–2 — отлично, защита работает по делу.
- Если зависания участятся — поднимаем приоритет на более глубокий fix в самой обвязке Chromium (отдельный апстрим-issue в patchright). Сейчас это пока единичный случай за месяц.
- Хроническая утечка Xvfb-дисплеев (8 штук висели с 4–12 мая ещё до этого инцидента — там Python падал чисто, но Xvfb-обёртка не убирала за собой). Это отдельный мелкий баг, тикет на чистку повешен в backlog.
Технические детали (для интересующихся)
PR'ы и коммиты:
- PR #276 — bash 2h stale-lockfile force-kill (
12f2dfe) - Issue #278 — диагноз Python-зависания
- PR #279 —
asyncio.wait_forна 4 close-вызова вBrowserClient(2483205)
Связанные инциденты в памяти системы:
- 2026-04-29/30 — антибот pravo.tech на kad.arbitr впервые поймали и научились ретраить через new_session() (PR #269)
- 2026-05-08 — каскадные определения не терялись после первого без СРО (T1 эпик)
Корневая причина: patchright/Chromium IPC может молчать неопределённо долго, когда страница в pravo.tech-«отравленном» состоянии или сломан node-pipe (EPIPE). Без жёстких таймаутов на этой стороне любой close-вызов потенциально бесконечный. До PR #279 это была единственная незащищённая поверхность в shutdown-пути.