Depuración de procesos busy

A veces, al ejecutar el comando php start.php status, podemos ver procesos en estado busy, lo que indica que el proceso correspondiente está manejando tareas. Normalmente, una vez que se completa el procesamiento de la tarea, el proceso debe volver a estar en estado idle. En general, esto no debería representar problemas. Sin embargo, si el proceso permanece en estado busy sin volver a estar idle, esto indica que hay un bloqueo o un bucle infinito dentro del proceso. A continuación se presentan métodos para localizar el problema.

Localización con strace + lsof

1. Encontrar el pid del proceso busy en el estado
Ejecute php start.php status y observe lo siguiente

El pid del proceso busy en la imagen es 11725 y 11748.

2. Rastrear el proceso con strace
Elija uno de los pids (aquí seleccionamos 11725), ejecute strace -ttp 11725 y obtendrá lo siguiente

Se puede ver que el proceso está en un bucle constante llamando poll([{fd=16, events=.... que es una llamada al sistema esperando eventos de lectura del descriptor de archivo 16, lo que significa que está esperando que este descriptor devuelva datos.

Si no se muestran llamadas al sistema, mantenga abierto el terminal actual, abra un nuevo terminal y ejecute kill -SIGALRM 11725 (envíe una señal de alarma al proceso), luego observe si el terminal de strace responde o si se encuentra bloqueado en alguna llamada al sistema. Si aún no muestra ninguna llamada al sistema, es probable que el programa esté atrapado en un bucle infinito de negocios. Consulte la sección en la parte inferior de la página sobre otras causas de que el proceso permanezca ocupado durante mucho tiempo en el punto 2 para resolverlo.

Si el sistema está bloqueado en una llamada al sistema epoll_wait o select, es una situación normal, lo que indica que el proceso ya se encuentra en estado idle.

3. Verificar descriptores de procesos con lsof
Ejecute lsof -nPp 11725 y observe lo siguiente

El descriptor 16 corresponde al registro 16u (última línea), se puede ver que el descriptor fd=16 es una conexión tcp, y la dirección remota es 101.37.136.135:80, lo que indica que el proceso está intentando acceder a un recurso http. El bucle constante en poll([{fd=16, events=.... está esperando que el servidor http devuelva datos, lo que explica por qué el proceso está en estado busy.

Solución:
Sabiendo dónde está bloqueado el proceso, es más fácil resolverlo. Por ejemplo, tras la localización, se debería verificar si el negocio está llamando a curl y si el url correspondiente no devuelve datos durante un largo período, lo que causa que el proceso espere indefinidamente. En este caso, se debe contactar al proveedor del url para ubicar la razón del retardo en la respuesta. Al mismo tiempo, se deberían agregar parámetros de tiempo de espera a la llamada curl, por ejemplo, un tiempo de espera de 2 segundos, para evitar que el proceso quede bloqueado indefinidamente (de esta manera, el proceso podría estar en estado busy por aproximadamente 2 segundos).

Otras causas de que un proceso esté ocupado durante mucho tiempo

Además del bloqueo del proceso que causa que esté en estado busy, hay otras razones que pueden llevar a que el proceso quede en estado busy.

1. Errores fatales en el negocio que provocan que el proceso salga continuamente
Fenómeno: En este caso, se puede ver que la carga del sistema es bastante alta, con un load average de 1 o más en el status. Se observa un número elevado y en aumento en el exit_count del proceso.
Solución: Ejecutar en modo depuración (php start.php start sin -d) el worker para ver errores de negocio y resolverlos.

2. Bucle infinito en el código
Fenómeno: Se puede ver que el proceso busy consume mucha CPU en el comando top y el comando strace -ttp pid no muestra información de llamadas al sistema.
Solución: Consulte el artículo de 'Niao Ge' para localizar problemas utilizando gdb y el código fuente de php. Los pasos son aproximadamente los siguientes:

  1. php -v para verificar la versión.
  2. Descargar el código fuente de la versión correspondiente de php.
  3. gdb --pid=pid del proceso busy.
  4. source ruta del código fuente de php/.gdbinit.
  5. zbacktrace para imprimir la pila de llamadas.
    En el último paso, se podrá ver la pila de llamadas actual del código php, es decir, la ubicación del bucle infinito en el código php.
    Nota: Si zbacktrace no muestra la pila de llamadas, es posible que al compilar php no se incluyera el parámetro -g, y será necesario recompilar php y reiniciar el worker para la localización.

3. Agregar temporizadores infinitamente
El código del negocio sigue agregando temporizadores sin eliminarlos, lo que provoca que el número de temporizadores dentro del proceso aumente indefinidamente, llevando finalmente a que el proceso ejecute temporizadores de manera infinita. Por ejemplo, el siguiente código:

$worker = new Worker;
$worker->onConnect = function($con){
    Timer::add(10, function(){});
};
Worker::runAll();

El código anterior agregará un temporizador una vez que un cliente se conecte, pero en todo el código del negocio no hay lógica para eliminar el temporizador, lo que hará que, con el tiempo, el número de temporizadores dentro del proceso continúe aumentando, lo que resultará en que el proceso ejecute temporizadores indefinidamente, quedando así busy.
Código correcto:

$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();