В двух из наших многочисленных модулей PHP (мы работаем под управлением Kubernetes) возникла проблема с утечкой сокетов Unix. Кажется, что наш процесс PHP CLI открывает, а не закрывает множество unix-сокетов, пока в несвязанном месте PHP не выйдет из строя с PHP Warning: stream_select(): You MUST recompile PHP with a larger value of FD_SETSIZE.
В других модулях, которые у нас есть, также есть открытые UNIX-сокеты, но в небольшом количестве, так что это не проблема.
Самое загадочное, что мы ни для чего не используем сокеты UNIX. Вся связь с другими процессами осуществляется исключительно через TCP/IP. Все розетки:
ESTABLISHED штатеss -x говорит, что на эти сокеты не было ни отправлено, ни получено данныхnetstat говорит, что у них всех есть RefCount из 3, если это что-нибудь значитss -xp также показывает, что оба конца каждого сокета удерживаются одним и тем же (одним и единственным) процессом PHP.Итак... по какой-то причине программа PHP создает сокеты UNIX, удерживает их открытыми, удерживая оба конца этого сокета (два разных файловых дескриптора) и не отпуская их.
Кроме того, в коде нет вызовов stream_socket_pair или socket_create_pair.
Из этого я понял, что эти сокеты создаются как побочный эффект какой-то другой встроенной функции PHP, которую я использую. CURL здесь является главным подозреваемым, но он также без проблем широко используется в других модулях. На самом деле я не могу вспомнить ничего, что эти два модуля делают такого, чего не делают другие.
Какие встроенные функции PHP (включая функции в расширениях, которые по умолчанию включены в PHP) могут привести к непреднамеренному созданию пары сокетов UNIX?
Добавлен:
ss -xp выглядит так:
# ss -xp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
u_str ESTAB 0 0 * 481304571 * 481304572 users:(("php",pid=7,fd=494))
u_str ESTAB 0 0 * 480934363 * 480934362 users:(("php",pid=7,fd=289))
u_str ESTAB 0 0 * 480899260 * 480899259 users:(("php",pid=7,fd=255))
u_str ESTAB 0 0 * 480853228 * 480853229 users:(("php",pid=7,fd=207))
u_str ESTAB 0 0 * 478659551 * 478659550 users:(("php",pid=7,fd=82))
u_str ESTAB 0 0 * 481263776 * 481263775 users:(("php",pid=7,fd=484))
u_str ESTAB 0 0 * 481014545 * 481014546 users:(("php",pid=7,fd=339))
u_str ESTAB 0 0 * 480847844 * 480847845 users:(("php",pid=7,fd=221))
u_str ESTAB 0 0 * 479446282 * 479446283 users:(("php",pid=7,fd=185))
u_str ESTAB 0 0 * 479430966 * 479430965 users:(("php",pid=7,fd=162))
u_str ESTAB 0 0 * 481391710 * 481391711 users:(("php",pid=7,fd=515))
u_str ESTAB 0 0 * 481013994 * 481013995 users:(("php",pid=7,fd=352))
u_str ESTAB 0 0 * 480884372 * 480884373 users:(("php",pid=7,fd=233))
.... many, may more lines ...
@Vivick - Но я думаю, что они будут отображаться как соединения TCP/IP, а не сокеты UNIX.
Также обратите внимание, что stream_select — это всего лишь системный вызов select для массива ресурсов PHP stream.
@Vivick - На самом деле TCP-соединения ДЕЙСТВИТЕЛЬНО отображаются как таковые. Если я наберу ss -p, то получу, например: tcp ESTAB 0 0 1.2.3.4:43652 5.6.7.8:17733 users:(("php",pid=7,fd=12))
Есть stream_socket_client звонки?
неблокирующие элементы в расширениях БД, похоже, используют сокеты и, следовательно, могут использоваться с такими функциями, как stream_select
@Вивик - Нет, нет, нет, stream_select не имеет к этому никакого отношения. Я сам использую его в несвязанном месте программы. Это то, что дает сбой, но только потому, что одновременно открыто очень много сокетов. Вопрос в том, что открывает все эти сокеты, а не закрывает? Потому что я не могу найти в своем коде ничего, что открывало бы сокеты UNIX. TCP-сокеты — да, конечно, много. Но не UNIX-сокеты. В моем коде нет ни одного места, которое могло бы открыть сокет UNIX, но по какой-то причине процесс со временем открывает сотни таких сокетов.
Нет, я говорю это потому, что это приводит к достижению предела файлового дескриптора. Расширения БД открывают сокеты при использовании в неблокирующем режиме.
Вы используете что-то похожее на Swoole?
@Vivick Нет, никакого Swoole или другого неблокирующего доступа к базе данных. Я использую Curl в неблокирующем режиме.
@Vivick Curl тоже был моим первым подозреваемым, но другие модули тоже без проблем используют его в неблокирующем режиме.
Все эти носки созданы pid=7? Pid=7 — это главный процесс или процесс, созданный вами?
@shingo Да, все по pid=7. В модуле есть только один процесс, и он не создает дочерние процессы. Оба конца всех сокетов Unix удерживаются этим процессом.
Вы пробовали использовать strace? Так как есть только один процесс.
просто WAG... Curl не выпускает в некоторых версиях, если только вы не unset канал.
@shingo - Нет. Я буду иметь в виду этот вариант, но он кажется довольно сложным, и я также не могу себе представить, какую полезную информацию я могу там найти. Возможно, если все остальное не поможет, я попробую это.
Отследите socket, чтобы знать, какая функция C ее вызывает.
@YvesLeBorg - Хорошо, но открывает ли он сокет домена unix для чего-либо без запроса? Например, если я попытаюсь открыть URL-адрес https://, может ли он также открыть сокет unix в дополнение к сокету tcp?
@shingo Мой код PHP... Я имею в виду, да, это, вероятно, выполнимо, но это будет чертовски сложно. Мне, вероятно, придется проследить и через собственные источники PHP. Как я уже сказал, я оставлю этот вариант открытым в крайнем случае.
Нет, я думаю, это, вероятно, не будет чертовски сложно. Создание носков Unix будет отображаться как socket(AT_UNIX... в strace, поскольку вы их не используете. Если вы видите какой-либо вызов, это виновник. С помощью stacktrace вы можете узнать соответствующую функцию C.
@shingo - Хм... Да, я увижу соответствующую функцию C, но она будет внутри самого PHP (или одного из его расширений). Или, зная, насколько продвинут PHP в наши дни, это также может быть какой-то динамически генерируемый ассемблерный код. В любом случае, как мне потом отследить его до моего PHP-кода?
@shingo - Хорошо, похоже, в этом нет необходимости. Путем экспериментов я обнаружил, что пары сокетов создаются Curl всякий раз, когда я делаю веб-запросы. Пока только одна пара, но я уверен, что добавление реальной нагрузки и множества параллельных запросов (как в затронутых модулях) может привести к созданию нескольких пар. Реальный вопрос: почему он вообще это делает, когда я делаю HTTP-запросы, а не запросы сокетов Unix? Кроме того, это происходит только в Kubernetes, а не на моей локальной машине.






Я нашел ответ сам. Публикую здесь, чтобы помочь тому, кто придет в следующий раз. Виновник... *барабанная дробь*... КУРЛ. В частности, curl_multi_init() в PHP 8.2 открывает пару UNIX-сокетов, предположительно для какой-то межпотоковой синхронизации.
Вот фрагмент кода, который это воспроизводит:
$handles = [];
for ( $i = 0; $i < 10; $i++ )
$handles[] = curl_multi_init();
echo "Sleeping...";
sleep(10); // Here we have 20 unix sockets open
foreach ($handles as $h)
curl_multi_close($h); // According to PHP docs, this doesn't do anything in PHP 8, but it's good style anyway.
$handles = [];
echo "Sleeping again...";
sleep(10); // Here we have 2 unix sockets open... because the $h variable still holds the last handle
echo "Goodbye!";
Я не знаю, почему такого поведения нет на моем локальном компьютере. Возможно, это как-то связано с конкретной версией CURL или PHP. В частности:
У меня PHP 8.2.13 с CURL 8.5.0 (с этим нет проблем), а у модуля Kubernetes — PHP 8.2.16 с CURL 7.88.1 (с этим есть проблема).
Если кто-то знает, почему этого не происходит на моей машине, пожалуйста, дайте мне знать!
Хорошо, теперь просто найти свою собственную CurlMultiHandle утечку — это гораздо более выполнимая задача и выходит за рамки этого вопроса.
Я предполагаю, что TCP-соединения, которые никогда не закрывались (запрос клиента, завиток и т. д.)