Соединения через сокет-активацию висят и не закрываются
Соединение, оборванное без закрытия, остаётся в таблице ядра часами. Для служб с сокет-активацией это означает исчерпание пределов соединений при отсутствии клиентов. Проверка живости включается в описании сокета и решает задачу без правок в самой службе.
Вероятные причины
По порядку: сверху то, что встречается чаще.
-
Проверка живости соединений не включена
Без неё оборванное соединение остаётся открытым до истечения длинных сроков ядра.
-
Соединения копятся в состоянии ожидания закрытия
Другая сторона закрыла соединение, а служба не читает до конца. Записи держатся до истечения срока.
-
Предел соединений исчерпан висящими записями
Предел считает все соединения, включая мёртвые. Новые клиенты получают отказ при отсутствии живой нагрузки.
Диагностика
Команды идут в том порядке, в котором их стоит выполнять: каждая следующая проверяет то, что осталось после предыдущей.
Соединения на порту службы и их состояния.
ss -tanp | grep :8080 | head -20Проверка живости и пределы соединений.
systemctl show myapp.socket -p KeepAlive -p MaxConnections -p NConnectionsРаспределение состояний соединений по машине.
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn | headРешение
Решение своё для каждой причины. Сначала определите, какая из них ваша, — иначе правки наложатся друг на друга.
- Почему происходит
- Без неё оборванное соединение остаётся открытым до истечения длинных сроков ядра.
- Как проверить
-
Посмотрите параметры сокета и число соединений.
systemctl show myapp.socket -p KeepAlive -p KeepAliveTimeSec ss -tan state established | wc -l
- Как исправить
-
Включите проверку живости и задайте разумные сроки. Это правильнее, чем менять общие настройки ядра для всей машины.
[Socket] ListenStream=8080 KeepAlive=true KeepAliveTimeSec=120
- Почему происходит
- Другая сторона закрыла соединение, а служба не читает до конца. Записи держатся до истечения срока.
- Как проверить
-
Посмотрите распределение состояний соединений.
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn | head
- Как исправить
- Исправьте закрытие соединений в самой службе. Проверка живости помогает при обрывах, но не заменяет корректного закрытия.
- Почему происходит
- Предел считает все соединения, включая мёртвые. Новые клиенты получают отказ при отсутствии живой нагрузки.
- Как проверить
-
Посмотрите предел и число соединений.
systemctl show myapp.socket -p MaxConnections -p NConnections
- Как исправить
- Поднимите предел и включите проверку живости. Одно без другого лишь отодвигает проблему.
Пример вывода
Предел исчерпан висящими соединениями. Пример показательный: он собран на тестовой машине специально для этой страницы, а не взят из чужого журнала.
systemd[1]: myapp.socket: Too many incoming connections (64), dropping connection.
$ ss -tanp | grep -c ":8080.*ESTAB"
64
$ ss -tanp | grep ":8080" | head -2
ESTAB 0 0 192.0.2.10:8080 192.0.2.55:52310
ESTAB 0 0 192.0.2.10:8080 192.0.2.55:52311
Связанные ошибки
- Сокет отклоняет соединения: достигнут MaxConnections При Accept=yes число одновременных экземпляров ограничено. Как поднять предел и когда режим по соединению не подходит.
- TCP: out of memory -- consider tuning tcp_mem Ядру не хватает памяти под сетевые буферы: соединения рвутся, служба отвечает с перебоями.
- Too many open files в журнале службы Служба исчерпала лимит файловых дескрипторов. Как правильно поднять LimitNOFILE и когда дело в утечке.
- No buffer space available в журнале службы Ошибка 105: нет места в буферах ядра. Разбор для сети и таблиц соседей.
- nf_conntrack: table full, dropping packet Соединения обрываются под нагрузкой: заполнена таблица отслеживания соединений ядра.
- Сокет не принимает данные: не тот тип Служба не получает пакеты: объявлен потоковый сокет вместо датаграммного или наоборот.
- Сокет слушает только IPv6 или только IPv4 неожиданно Клиенты по одному из протоколов не подключаются: поведение двойного стека задаётся отдельным параметром.
- Address already in use при запуске службы Порт или адрес уже занят: bind() failed (98: Address already in use). Как найти владельца порта и что делать с остатками прежнего процесса.
Источники
-
systemd.socket(5)
KeepAlive=, KeepAliveTimeSec= и пределы соединений. -
Воспроизведено на тестовой машине, systemd 255 (Ubuntu 24.04)
Проверено на systemd 255: без проверки живости оборванные соединения держат предел.