CLOSE_WAIT растёт: почему перезапуск сети не закрывает сокеты приложения

После нескольких часов работы у сервиса накапливаются соединения CLOSE_WAIT. Снаружи это выглядит как сетевой сбой: новые операции замедляются или завершаются ошибками. Команда начинает уменьшать TCP-таймауты. Но это состояние указывает на определённый этап: удалённая сторона уже сообщила о завершении своей передачи, а локальное приложение ещё не закрыло соответствующий сокет.

Отделите CLOSE_WAIT от TIME_WAIT

ss -tan state close-wait
ss -tanp state close-wait

TIME_WAIT и CLOSE_WAIT похожи названием, но требуют разных действий. TIME_WAIT связан с завершением TCP и защитой от старых сегментов. CLOSE_WAIT означает ожидание локального закрытия после полученного FIN. Поэтому совет уменьшить параметр для другого состояния не устраняет причину накопления CLOSE_WAIT.

Опция p помогает найти процесс, если права позволяют показать владельца. Сопоставьте PID с нужным экземпляром приложения. Внешний адрес тоже важен: все незакрытые соединения могут вести к одному поставщику либо относиться к входящим клиентам. Эти два сценария ведут к разным участкам кода и разным владельцам исправления.

Проверьте, остаются ли старые соединения

Снимите состояние перед серией одинаковых запросов на тестовом стенде. Повторите операции, затем дождитесь штатного завершения. Если число CLOSE_WAIT растёт после каждой серии и не возвращается к исходному уровню, сохраните соответствующие пары адресов. Одно кратковременное состояние при нормальном закрытии ещё не означает утечку.

Например, после первой серии осталось пять соединений, после второй — десять, причём ранние пары адресов не исчезли. Это уже лучшее воспроизведение, чем единичный снимок с большим числом строк. Оно связывает накопление с конкретной операцией и позволяет проверить изменение приложения при той же нагрузке.

Посмотрите, что делает приложение после закрытия удалённой стороны. Обработчик может получить конец потока, попасть в ветку ошибки и пропустить освобождение соединения. В HTTP-клиенте это нередко связано с жизненным циклом тела ответа; в собственном TCP-протоколе — с выходом из цикла чтения. Конкретное исправление зависит от библиотеки, поэтому данные Linux должны привести к нужной операции.

Передайте разработчику проверяемый сценарий

Полезный набор — версия приложения, действие, после которого появляется сокет, удалённая сторона, временные снимки ss и журналы той же операции. Если можно управлять тестовым сервером, воспроизведите штатное закрытие и закрытие на ошибке отдельно. Это поможет найти ветку, в которой ресурс остаётся открытым.

Перезапуск процесса временно освободит его сокеты, но не объяснит их происхождение. После изменения кода повторите тот же сценарий без перезапуска между сериями. Хороший результат — число соединений перестаёт расти, операции завершаются, а штатный слушатель остаётся доступен.

Не закрывайте чужие сокеты вручную в качестве диагностики: это обрывает текущие операции и стирает наблюдаемое состояние. Сначала сохраните данные, затем выполняйте согласованный способ восстановления сервиса. Так технический разбор переживёт временную меру и приведёт к проверке причины.

Свяжите сетевое состояние с поведением процесса. Откройте программу Linux и Bash для DevOps-инженеров: курс включает процессы, сеть и разбор типичных сбоев сервиса. Такая связка нужна, чтобы передавать разработчику предметное воспроизведение, а не только название TCP-состояния.

Ещё по теме

Продолжить на практике

  • Linux и Bash для DevOps-инженеров ↗

    Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.