PunchInfantino hails Flamingos’ historic U-17 World cup triumphCNN TürkYeni Parti'de ön seçim heyecanı! Gözler sandıktaDaily MaverickSOUTH AFRICAN CONNECTION: How an anglophone education in Durban shaped Fernando Pessoa, giant of European literatureThe Jerusalem PostIDF faces severe manpower crisis as recently released reservists called back to duty한겨레11월 9일 한겨레 그림판ColliderTom Hanks’ WWII Thriller Masterpiece Officially Crosses Massive MilestoneZDF heuteAktuelle Pressemitteilungen des ZDFSRF NewsParmelin-Nachfolge – Mit Aeschi erhält das Kandidatenfeld klare KonturenNHK 社会山林で女性遺体 女性が相談も警察は意向受け警告行わずWirtualna PolskaPotrzebna natychmiastowa pomoc. Ukraina na krawędzi kryzysuHabertürkValencia 90+6'da attı Boca şampiyon olduESPNMessi makes history as 1st back-to-back MLS Golden Boot winner
The Daily Newsstand · Free, Always
Sunday, November 8, 2026

Too many open files без открытых документов: как сокеты съедают лимит процесса

Translate

Сервис перестал принимать клиентов. В журнале - Too many open files. Больших логов нет, каталоги с данными выглядят нормально, а ulimit -n в вашей консоли показывает огромный запас. Откуда взялись «слишком многие файлы»?

У этой ошибки есть языковая ловушка: слово files заставляет искать обычные файлы на диске. Но сокет тоже занимает файловый дескриптор. Сервис может упереться в лимит, создавая только сокеты - даже без подключения к удалённому серверу. Ниже я воспроизведу именно это, затем подниму лимит и получу ту же ошибку повторно.

Поставим маленький предел одному процессу

Для эксперимента достаточно Linux и Python. Отдельному дочернему процессу задаю soft limit RLIMIT_NOFILE=64 и hard limit 256. Родитель сохраняет свой исходный лимит. Дочерний процесс создаёт TCP-сокеты и удерживает ссылки на все объекты, поэтому они остаются открытыми. Он не вызывает bind(), listen() или connect() - сетевого обмена здесь вообще нет.

В этом запуске у родителя:

$ ulimit -n
1048576

А у дочернего процесса, которого я собираюсь сломать:

Max open files            64                   256                  files

Это строка из его /proc/PID/limits. После накопления сокетов следующий socket() возвращает:

[Errno 24] Too many open files

В /proc/PID/fd насчитывается 64 записи. Из них 61 - ссылки вида socket:[число], ещё три - стандартные потоки ввода и вывода. Обычных пользовательских файлов процесс для этого не открывал. Ниже - настоящий снимок xterm с этим запуском и дальнейшими проверками.

Важная точность: RLIMIT_NOFILE ограничивает допустимый номер нового дескриптора - он должен быть меньше лимита. На нашем стенде номера заняты подряд от 0 до 63, поэтому в момент отказа видны ровно 64 открытых дескриптора. Для произвольного процесса число записей и границу допустимых номеров не всегда можно отождествлять.

Что скрывается за словом files

Файловый дескриптор - небольшой целый номер, через который процесс обращается к открытому ресурсу. В этой таблице живут обычные файлы, сокеты, каналы и другие объекты.

Для сетевого сервиса это меняет направление диагностики. Тысяча удерживаемых клиентских соединений требует дескрипторов, даже если сервис не читает тысячу документов. Дополнительные ресурсы нужны слушающим сокетам, соединениям с БД, логам и механизму обработки событий. Поэтому размеры каталога с файлами почти ничего не говорят о запасе работающего процесса по этому лимиту.

Посмотреть содержимое таблицы можно так:

ls -l /proc/PID/fd

Ссылки вида socket:[…], pipe:[…] и anon_inode:[…] помогают понять, что удерживается. На многопоточном или загруженном процессе это движущийся снимок: дескрипторы могут открываться и закрываться прямо во время просмотра.

Подняли лимит. Почему отказ вернулся?

Не меняя код накопления, поднимаю soft limit того же дочернего процесса с 64 до 128. Hard limit остаётся 256. Скрипт продолжает создавать и удерживать сокеты. Через дополнительные 64 успешных создания снова получаем EMFILE.

Теперь в таблице 128 дескрипторов: 125 сокетов и три стандартных потока. Повышение сработало - процесс действительно получил запас. Но запас закончился, потому что жизненный цикл ресурсов остался прежним.

Затем закрываю удерживаемые сокеты и выполняю 500 операций создания и закрытия:

for _ in range(500):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM):
        pass

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

Почему ваш ulimit не оправдывает сервис

Команда ulimit -n в SSH-сессии показывает лимит оболочки. Её будущие дочерние процессы обычно наследуют этот предел, но уже запущенный сервис - другой процесс со своей историей запуска. В эксперименте это видно непосредственно: у родителя 1048576, у дочернего процесса 64, и ошибка возникает именно у дочернего.

Для systemd-сервиса начинаю с фактического PID:

pid=$(systemctl show -p MainPID --value notify-gateway)
grep 'Max open files' /proc/"$pid"/limits
ls -l /proc/"$pid"/fd

Если MainPID равен 0, сначала выясняю, почему сервис не запущен. Если приложение создаёт рабочие процессы, проверяю также PID того воркера, у которого появилась ошибка. Доступ к чужим дескрипторам может требовать прав; изучать нужно процесс в нужном PID namespace.

Затем сравниваю реальность с настройкой запуска:

systemctl cat notify-gateway
systemctl show notify-gateway -p LimitNOFILE -p LimitNOFILESoft

Для системной службы лимит обычно задают LimitNOFILE= в unit или drop-in. Обычная правка /etc/security/limits.conf, применяемая через PAM к сессиям, сама по себе не настраивает типичную системную службу. После изменения unit нужны daemon-reload и новый запуск процесса; затем лимит проверяется в /proc снова.

Конкретное число следует подбирать под приложение. Например, старый код с select() может не поддерживать номера дескрипторов выше 1023: бездумно увеличивать предел такому процессу опасно. Для пользовательских systemd-служб действует ещё и ограничение, унаследованное их менеджером. Универсальная команда «поставь 65536» скрывает слишком много условий.

Недостаточный запас или утечка?

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

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

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

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

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.