Отладка занятых процессов
Иногда при выполнении команды php start.php status
мы видим, что некоторые процессы находятся в состоянии busy
, что указывает на то, что соответствующий процесс занят выполнением бизнес-логики. В нормальной ситуации по завершению обработки бизнес-логики процесс должен вернуться в состояние idle
, и обычно это не вызывает проблем. Однако, если процесс постоянно находится в состоянии занятости и не возвращается в состояние idle
, это указывает на блокировку или бесконечный цикл внутри процесса. Вы можете найти причину с помощью следующих методов.
Поиск с использованием команды strace+lsof
1. Найти PID занятого процесса в статусе
После выполнения команды php start.php status
, вы увидите результаты, подобные ниже:
На картинке процессы в состоянии busy
имеют pid
11725 и 11748.
2. Отслеживание процесса с помощью strace
Выбираем процесс с pid
(например, 11725) и запускаем команду strace -ttp 11725
. Результат будет подобен следующему:
Вы увидите, что процесс находится в бесконечном цикле системного вызова poll([{fd=16, events=....
, ожидая доступности событий для дескриптора файлов fd=16.
Если не отображается никаких системных вызовов, оставьте текущий терминал, откройте новый терминал и выполните команду kill -SIGALRM 11725
(отправить процессу сигнал будильника), затем проверьте, отобразится ли ответ в терминале strace, указывающий на блокировку в каком-либо системном вызове. Если по-прежнему не отображается никаких системных вызовов, это означает, что программа, скорее всего, находится в бесконечном цикле бизнес-логики, см. пункт 2 раздела «Решение длительного состояния занятости процесса» в конце страницы.
Если система блокируется на системных вызовах epoll_wait или select, это означает, что процесс уже находится в состоянии idle
.
3. Просмотр дескрипторов процесса с помощью lsof
Выполните команду lsof -nPp 11725
, результат будет подобен следующему:
Дескриптор 16 соответствует записи 16u (последняя строка), где можно увидеть, что дескриптор fd=16 представляет собой TCP-соединение с удаленным адресом 101.37.136.135:80
, что указывает на то, что процесс, вероятно, ожидает ответа от HTTP-сервера. Это объясняет, почему процесс находится в состоянии busy
.
Решение:
После определения местонахождения блокировки в бизнес-логике необходимо легко решить проблему. Например, в случае, описанном выше, процесс, вероятно, ждет ответ от сервера при вызове curl, и провайдеру url следует устранить причину медленного ответа по url. Также при вызове curl следует добавить параметр таймаута, например, если ответ не был получен в течение 2 секунд, тогда запрос завершится, что предотвратит длительную блокировку.
Другие причины длительного состояния занятости процесса
Помимо блокировки бизнес-логики, существует несколько других причин, вызывающих простой процесса в состоянии busy
.
1. Причина - фатальная ошибка в бизнес-логике, приводящая к постоянному выходу из процесса
Симптомы: В этом случае вы увидите относительно высокую нагрузку на систему, и значение load average
в состоянии status
будет равно 1 или выше. Количество выходов из процесса (exit_count
) будет очень высоким и будет постоянно увеличиваться.
Решение: Запустите workerman в режиме отладки (без флага -d
) с помощью команды php start.php start
, чтобы увидеть ошибки бизнес-логики и устранить их.
2. Бесконечный цикл в коде
Симптомы: Вывод команды top
покажет, что занятый процесс потребляет большое количество CPU, и команда strace -ttp pid
не выведет информацию о системных вызовах.
Решение: Следуйте инструкциям из статьи от "птичьего пера" с использованием gdb и исходного кода PHP. В общем, шаги выглядят так:
- Используйте команду
php -v
для просмотра версии. - Скачайте исходный код PHP.
- Выполните команду
gdb --pid=pid занятого процесса
. - Выполните команду
source путь_к_исходному_коду_PHP/.gdbinit
. - Выполните команду
zbacktrace
для вывода стека вызовов.
Последний шаг позволит увидеть, на какой строке кода PHP происходит бесконечный цикл.
Примечание: Если zbacktrace
не показывает стек вызовов, возможно, при компиляции PHP не был использован флаг -g
. В этом случае вам придется перекомпилировать PHP и затем перезапустить workerman для локализации проблемы.
3. Бесконечное добавление таймеров
Если бизнес-логика вашего приложения постоянно добавляет таймеры, но не удаляет их, это может привести к накоплению большого количества таймеров внутри процесса, в конечном итоге вызывая бесконечное исполнение таймеров.
Например:
$worker = new Worker;
$worker->onConnect = function($con){
Timer::add(10, function(){});
};
Worker::runAll();
В этом коде при каждом соединении клиента добавляется новый таймер, и если логика не включает удаление таймеров, то с течением времени в процессе будет накапливаться все больше и больше таймеров, что в конечном итоге приведет к постоянному исполнению таймеров и состоянию занятости.
Вот исправленный вариант кода:
$worker = new Worker;
$worker->onConnect = function($con){
$con->timer_id = Timer::add(10, function(){});
};
$worker->onClose = function($con){
Timer::del($con->timer_id);
};
Worker::runAll();