Les signaux POSIX et PHP

Après la conférence d’Alexandre Balmes sur les signaux, les PID et leur gestion en PHP au ForumPHP 2017, nous avons souhaité écrire un article récapitulatif de ce que sont les signaux, comment ils fonctionnent au niveau système, et comment les utiliser en PHP.

Les signaux et process identifier (PID) sont souvent méconnus des développeurs PHP. Pourtant, ils sont très utiles et même indispensables lorsque nous devons travailler avec des tâches qui vont s’exécuter via la ligne de commande : un script d’import de données, un cron ou un worker RabbitMQ.

PID

Mais qu’est-ce qu’un PID ? Ce n’est rien de plus qu’un identifiant. Chaque processus en possède un. Ils commencent à 1 et vont en s’incrémentant. Le processus ayant pour PID 1 est donc le premier processus à s’être lancé sur le système. Sur une majorité de distributions Linux, ce processus sera systemd. Pour rappel, systemd est désormais le gestionnaire d’initialisation des processus sous Debian depuis sa version 8 et sur Red Hat depuis sa version 7. La commande ps -q 1 permet de vérifier quel est le gestionnaire de processus.

Vous vous êtes peut-être demandé à quoi correspondent les dossiers dans le dossier /proc de votre système ? À chaque dossier correspond un processus. Chaque dossier a pour nom le PID du processus. Dans ce dossier, nous pouvons retrouver entre autres, les variables d’environnement qui ont été passées au processus.

Certaines tâches ne doivent pas pouvoir s’exécuter en parallèle. Nous pourrions utiliser le PID pour bloquer une deuxième commande qui voudrait se lancer. Cela dit, la meilleure solution est d’utiliser flock s’il n’y a qu’un seul serveur qui exécute le code. Symfony a une implémentation de la version 2.6 à 3.4. Celle-ci a été dépréciée et remplacée par le composant Lock à partir de la version 3.4. Ce composant permet entre autres d’avoir des locks distribués. C’est notamment utile quand le code peut s’exécuter sur plusieurs serveurs.

Signaux

Pour commencer, il faut savoir que les signaux ne sont pas vraiment supportés par Windows. En effet, Windows n’utilise pas la norme POSIX.

Qu’est-ce qu’un signal ? Pour faire simple, c’est une notification envoyée par le système d’exploitation, un utilisateur du système, ou un autre programme à un processus de manière asynchrone. Par exemple, c’est utilisé pour dire à un processus qu’il doit s’arrêter proprement. Il existe 31 signaux différents et à chaque signal, son utilité.

Pour arrêter proprement un processus, il faut envoyer un SIGINT via la commande kill -2 PID ou via CTRL+C pour annoncer au processus qu’il doit s’arrêter. Pour rappel, CTRL+C est un raccourci clavier qui va envoyer un SIGINT (2) au processus qui est en train de tourner. Le processus va alors recevoir ce signal et pourra alors décider du meilleur moment pour s’arrêter. Tout ceci afin qu’il ne coupe pas en plein milieu d’une transaction par exemple.

kill -9

Quand les utiliser ?

L’utilisation des signaux est très utile pour garantir l’intégrité des donnés manipulées par un programme. Ils permettent d’éviter qu’un autre processus ou qu’un humain arrête le processus en plein milieu d’un traitement. Imaginons qu’un programme doive s’occuper de la facturation périodique d’un site. Ce programme a pour but de sélectionner dans la BDD les abonnements qui doivent être renouvelés, appeler une API de paiement pour procéder à la transaction, envoyer un email à l’utilisateur puis enfin mettre à jour la BDD. Il est évident qu’il ne faut pas que le programme puisse s’arrêter en plein milieu du traitement.

Comment les utiliser en PHP ?

Pour pouvoir utiliser les signaux en PHP, l’extension pecl pcntl est nécessaire.

L’utilisation est très simple. Il faut d’abord installer un gestionnaire de signaux grâce à la fonction pcntl_signal. Ce n’est rien de plus qu’une fonction qui sera appelée quand PHP annoncera qu’un signal est arrivé. En effet, PHP va bufferiser les signaux qu’il reçoit. C’est seulement lors de l’appel à la fonction pcntl_signal_dispatch que le gestionnaire de signaux sera exécuté.

Voici un exemple de code qui tire parti de ces deux fonctions. Si vous essayez de l’exécuter, vous noterez qu’il y a toujours un ABC qui s’affiche quel que soit le moment ou vous utilisez CTRL+C.

class Daemon
{
    // Par défaut, nous n'arrêtons pas le processus.
    private $shouldStop = false;

    public function __construct()
    {
        // Nous installons un gestionnaire de signaux qui va capturer les signaux SIGINT (CTRL+C)
        pcntl_signal(SIGINT, function () {
            // Au moment où PHP va dispatcher le signal, nous allons stocker le fait
            // qu'il faut s'arrêter.
            $this->shouldStop = true;
        });
    }

    public function run()
    {
        while (true) {
            // Boucle de traitement
            echo "A";

            sleep(1);

            echo "B";

            // Dispatch des signaux qui ont été buffurisés par PHP
            pcntl_signal_dispatch();

            sleep(1);

            echo "C\n";

            // Si un signal a été dispatché, alors nous pouvons arrêter proprement le script
            // car celui-ci a réellement fini de travailler.
            if ($this->shouldStop) {
                echo "\nThe program is going to quit.\n";

                break;
            }
        }
    }
}

$daemon = new Daemon();
$daemon->run();

Remarques

Vous verrez peut-être dans certains scripts PHP l’utilisation des ticks pour contrôler le buffering des signaux, il ne faut surtout pas utiliser ce mécanisme car vous n’aurez aucun contrôle sur ce dispatch. Tout l’intérêt des signaux est justement de contrôler quand ce dispatch a lieu.

Depuis PHP 7.1, il est possible de se passer de la fonction pcntl_signal_dispatch. Il suffit d’appeler tout au début du script pcntl_async_signals(true). Mais ce système a le même inconvénient que l’usage des ticks. Néanmoins, grâce à une variable shouldStop comme dans l’exemple précédent, nous contrôlons le moment où le code peut s’arrêter. L’usage de pcntl_async_signals(true) n’est donc pas vraiment un problème. Mais le code peut ne pas être toujours aussi simple.

Conclusion

Au même titre que les codes de retour, les signaux et les PID sont les deux pierres angulaire de la gestion de processus sous Unix. Et vos tâches PHP doivent les exploiter pour gagner en robustesse !

Symfony 4.1 proposera sûrement un nouveau composant dédié pour gérer les workers en PHP et il utilisera évidemment les signaux.

blog comments powered by Disqus