Фоновый сервис жёг ядро в тишине логов: осиротевшая горутина в Go

·

4 мин чтения

Как одна осиротевшая go-рутина научила меня искать проблему не в том, что работает, а в том, что подозрительно молчит.

Всё должно было закончиться на финальных титрах. Фильм досмотрен, Apple TV выключен, TorrServer на удалённом iMac должен был просто замереть в ожидании следующего сеанса. Но кулер старого сервера начал нарастать по оборотам, переходя в ровный, тревожный гул.

Заглянул в мониторинг: один поток процессора выжжен в ноль. 104% нагрузки. Постоянно. Часами. При этом в интерфейсе сервера тишина - активных закачек нет, торренты висят в статусе «в базе», трафик нулевой.

Лечил здоровую сеть

Первым делом полез в сетевой стек. Подумал, что какой-нибудь кривой пир зациклил запросы или туннель решил устроить DDoS-атаку на самого себя. Проверил логи сетевого шлюза - чисто. Пакеты не летают, интерфейсы спят.

Потом решил, что дело в самом движке торрентов - может, завис какой-то внутренний процесс перехеширования данных? Полез в API сервера, пытался принудительно сбросить кэш и удалить торренты из списка. Но API отвечал корректно, а загрузка CPU даже не шелохнулась.

Самое странное было в логах самого TorrServer. Я ожидал увидеть там бесконечный поток ошибок, панику или хотя бы предупреждения о переполнении буфера. Но лог был мёртв. После строки Torrent close by timeout и закрытия кэша не появилось больше ни единого символа.

Система рапортовала, что всё в порядке. Процессор при этом пытался пробить потолок. Это была идеальная ловушка: приборы показывают «зелёный», а железо раскаляется.

Когда молчание - это симптом

Когда логика «ищи ошибку в тексте» зашла в тупик, я скормил ситуацию ИИ. Не просил починить код - попросил помочь интерпретировать эту странную тишину.

И тут пришёл вопрос, который перевернул диагностику:

«А что если тишина в логе - это не признак покоя, а симптом того, что поток зациклился внутри функции, которая вообще не умеет писать в лог?»

Это был момент истины. Я слишком привык, что любая проблема оставляет след в текстовом файле. Но если горутина (легковесный поток в Go) «осиротела» и застряла в бесконечном цикле ожидания закрытого канала или пустом спинере, она будет жрать процессор, не произнося ни слова.

Оказалось, это известный баг движка anacrolix/torrent. После разрыва соединения и таймаута одна из горутин иногда просто забывает выйти. Она крутится вхолостую, перепрыгивая с одного системного потока на другой, создавая иллюзию нагрузки, но не совершая никакой полезной работы. Она была жива, но абсолютно нема и бесполезна.

Сторож для немого призрака

Лечить такой баг в чужом бинарнике без исходников - затея для мазохистов. Проще было создать внешний «надсмотрщик», который бы понимал, когда тишина в логах становится подозрительной.

Я написал простой watchdog на bash и обернул его в launchd. Логика простая: сервис считает «сердцебиением» периодические системные сообщения (LPD-эхо), которые приходят каждые 5 минут. Если лог молчит дольше 5.5 минут, а CPU при этом всё ещё зашкаливает за 80% - значит, мы поймали того самого «призрака».

# Фрагмент логики watchdog.sh
if [[ $active_torrents -eq 0 ]] && [[ $cpu_usage -gt 80 ]]; then
    launchctl kickstart -k gui/501/com.torrserver
fi

Сторожевой пес теперь просто перезапускает сервис, когда тот уходит в «безумный сон». TorrServer поднимается мгновенно, торренты остаются в базе, а я больше не слышу, как кулер iMac пытается улететь в стратосферу после каждого фильма.

Самое громкое - в тишине

Этот кейс оставил одну мысль - про доверие к логам. Мы привыкли считать их единственным источником правды. Но лог - это то, что разработчик захотел нам рассказать.

Молчание лога при явном симптоме (нагрев, нагрузка) - это самый громкий сигнал о том, что проблема лежит глубже уровня приложения. Она лежит на уровне управления потоками и памяти, там, где абстракции начинают течь.

ИИ и здесь сработал не подсказкой, а сменой оптики: помог увидеть моё же допущение: «если в логах пусто, значит ничего не происходит». Оказалось, что самое страшное часто происходит именно в этой пустоте.

Как зашло?