Déboguer les processus busy

Parfois, en exécutant la commande php start.php status, nous pouvons voir des processus en état busy, ce qui indique que le processus correspondant est en train de traiter des tâches. En temps normal, une fois le traitement des tâches terminé, le processus revient à l'état idle. Dans la plupart des cas, cela ne pose pas de problème. Cependant, si le processus reste constamment en état busy sans revenir à l'état idle, cela signifie qu'il y a un blocage ou une boucle infinie dans le traitement. Nous pouvons localiser le problème à l'aide des méthodes suivantes.

Localisation à l'aide des commandes strace+lsof

1. Trouver le pid du processus busy dans le status
Après avoir exécuté php start.php status, le résultat affichera comme suit :

Le pid des processus en état busy est 11725 et 11748.

2. Suivre le processus avec strace
Choisissez un processus pid (nous choisissons ici 11725), exécutez strace -ttp 11725, et le résultat s'affichera comme suit :

Nous pouvons voir que le processus est en boucle sur l'appel système poll([{fd=16, events=.... ; cela signifie qu'il attend un événement de lecture pour le descripteur fd 16, ce qui équivaut à attendre que ce descripteur retourne des données.

S'il n'y a aucun appel système affiché, gardez le terminal actuel ouvert, ouvrez un nouveau terminal et exécutez kill -SIGALRM 11725 (envoi d'un signal de minuterie au processus), puis vérifiez si le terminal strace répond et s'il est bloqué sur un appel système. Si aucun appel système n'est affiché, cela indique que le programme est très probablement dans une boucle décédée, référez-vous à la section des autres causes de longue durée en état busy au bas de la page pour plus de détails.

Si le système est bloqué dans un appel système epoll_wait ou select, cela est considéré comme normal, ce qui indique que le processus est déjà en état idle.

3. Vérifier les descripteurs de processus avec lsof
Exécutez lsof -nPp 11725, et le résultat s'affichera comme suit :

Le descripteur 16 correspond à l'enregistrement 16u (dernière ligne), nous pouvons voir que le descripteur fd=16 est une connexion tcp, avec l'adresse distante 101.37.136.135:80, indiquant que le processus est probablement en train d'accéder à une ressource http. La boucle poll([{fd=16, events=.... est en attente de la réponse du serveur http, ce qui explique pourquoi le processus est en état busy.

Solution :
Sachant où le processus est bloqué, il devient alors plus facile de résoudre le problème, par exemple dans le cas où le processus est en attente d'une réponse longue d'un appel curl pour une certaine URL. Nous pouvons contacter le fournisseur de l'URL pour identifier la cause du retard, et il est également recommandé d'ajouter un paramètre de délai d'attente lors de l'appel à curl, par exemple, timmer après 2 secondes si aucune réponse n'est reçue, afin d'éviter un blocage prolongé (il se peut que le processus se retrouve dans un état busy pendant environ 2 secondes).

Autres causes entraînant un état busy prolongé du processus

En plus du blocage du processus qui peut entraîner un état busy, d'autres raisons suivantes peuvent également provoquer cet état.

1. Erreurs fatales dans les tâches entraînant une sortie continue du processus
Phénomène : Dans ce cas, une charge système élevée peut être observée, le load average dans le status est de 1 ou plus. Nous pouvons voir que le nombre exit_count du processus est très élevé et continue d'augmenter.
Solution : Exécutez Workerman en mode débogage (php start.php start sans ajouter -d) pour voir les erreurs de traitement et corrigez-les.

2. Boucle infinie dans le code
Phénomène : Dans top, nous pouvons voir que les processus busy utilisent beaucoup de CPU, et la commande strace -ttp pid ne fournit aucune information sur les appels système.
Solution : Référez-vous à l'article de Maître Niao pour localiser à l'aide de gdb et du code source php. Les étapes résumées sont comme suit :

  1. Vérifiez la version avec php -v
  2. Téléchargez le code source correspondant à la version php
  3. Exécutez gdb --pid=pid du processus busy
  4. Exécutez source chemin du code source php/.gdbinit
  5. Exécutez zbacktrace pour imprimer la pile d'appels.
    À la dernière étape, vous pourrez voir la pile d'appels du code PHP actuellement exécuté, ce qui indique où se trouve la boucle infinie.
    Remarque : Si zbacktrace ne renvoie pas la pile d'appels, il se peut que votre compilation PHP n'inclut pas le paramètre -g, il sera donc nécessaire de recompiler PHP, puis de redémarrer Workerman pour localiser.

3. Ajout infini de minuteries
Le code métier ajoute constamment des minuteries sans les supprimer, entraînant une augmentation continue du nombre de minuteries dans le processus, ce qui finit par conduire à l'exécution infinie des minuteries. Par exemple, le code suivant :

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

Ce code augmentera le nombre de minuteries lorsqu'une connexion client est établie, mais il n'y a pas de logique pour supprimer les minuteries dans l'ensemble du code métier. Avec le temps, cela entraînera une augmentation du nombre de minuteries dans le processus, conduisant finalement à un état busy dû à l'exécution infinie des minuteries.
Le code correct :

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