L'RFC PHP qui définit une Polling API native a été approuvée et fusionnée dans la branche master du langage, offrant aux développeurs un moyen rapide, au niveau du système d'exploitation, de surveiller les descripteurs de fichiers. Couplé aux Fibers récemment ajoutées, PHP dispose désormais d'une base intégrée et efficace pour le code asynchrone — une lacune qui a longtemps forcé les bibliothèques à bricoler leurs propres solutions de contournement.
Les Fibers et la boucle d'événements, séparées
Les Fibers sont une primitive de flux de contrôle de bas niveau. Elles permettent à une fonction de suspendre son exécution à un point choisi et de reprendre plus tard exactement là où elle s'était arrêtée. Point crucial, une fiber ne sait rien des sockets, des timers ou de toute autre source d'E/S ; elle se contente de se mettre en pause et de redémarrer à la demande.
Une boucle d'événements (event loop) est l'ordonnanceur qui décide quand une fiber en pause doit être reprise. Dans une pile asynchrone typique, la boucle surveille un ensemble de descripteurs de fichiers, attend qu'ils deviennent lisibles ou écrits, puis réveille la fiber appropriée.
PHP possédait déjà les Fibers mais manquait d'un mécanisme natif pour demander au système d'exploitation quels descripteurs étaient prêts. Le résultat était une dépendance à stream_select() ou à des extensions externes, et chaque bibliothèque asynchrone finissait par écrire ses propres back-ends pour epoll sur Linux, kqueue sur BSD, IOCP sur Windows, etc.
La nouvelle Polling API comble cette lacune. Elle expose une fine couche d'abstraction (wrapper) autour des fonctionnalités de polling du système d'exploitation (epoll sur Linux, kqueue sur BSD/macOS, etc.). L'API ne remplace pas les Fibers ; elle donne simplement à la boucle d'événements la vitesse et l'évolutivité dont elle a besoin.
Pourquoi ce changement est important pour ReactPHP, Amp et leurs alliés
ReactPHP et Amp v3 ont construit leurs propres couches d'abstraction par-dessus les mécanismes de polling du système d'exploitation. Ces couches contiennent plusieurs chemins de code, chacun optimisé pour une plateforme spécifique, et elles doivent être maintenues en synchronisation avec les changements du noyau (kernel). Avec une Polling API native, les bibliothèques peuvent abandonner la majeure partie de cette tuyauterie et s'appuyer sur un seul appel fourni par le cœur du langage.
- Maintenance – Moins de branches spécifiques à une plateforme signifie moins de bugs et une surface d'attaque réduite pour les audits de sécurité.
- Performance – L'appel natif communique directement avec epoll/kqueue.
- Portabilité – Le code qui s'exécute sur un PHP « vanilla » bénéficie désormais de la même performance de base sur tous les systèmes d'exploitation pris en charge, sans nécessiter d'extensions optionnelles.
Swoole, en revanche, reste un remplacement complet de l'environnement d'exécution (runtime) qui embarque sa propre boucle d'événements et son propre système de coroutines. La Polling API n'affecte pas les compromis de Swoole ; les développeurs qui ont besoin d'une latence ultra-faible ou d'une gestion personnalisée de la mémoire continueront de le considérer comme une option distincte.
Un benchmark rapide en dit long
Un ordonnanceur minimal a récupéré plusieurs URL via des sockets bruts, en utilisant un unique Io\Poll\Context pour gérer plusieurs fibers. Deux observations ont émergé :
- Vitesse – L'ajout de requêtes concurrentes n'augmente pas le temps total écoulé ; le lot de requêtes se termine aussi rapidement que la requête individuelle la plus lente. En d'autres termes, le surcoût lié à la concurrence est pratiquement nul.
- Coût CPU – Avec
stream_select(), l'utilisation du CPU augmente de manière notable à mesure que le nombre de flux augmente, car la fonction doit itérer sur chaque descripteur à chaque appel. Le coût de la Polling API reste stable, de trois à trente flux, grâce à la capacité du noyau à surveiller de nombreux descripteurs en un seul appel système.
Ce que les développeurs devraient faire maintenant
- Adopter Amp v3 si vous préférez un style natif pour les fibers qui s'aligne directement avec la nouvelle API. Son interface publique est déjà mappée sur le poller sous-jacent, vous profitez donc de l'avantage sans avoir à réécrire votre code.
- Rester sur ReactPHP si vous aimez le contrôle explicite sur le cycle de vie de la boucle.
- Éviter les ordonnanceurs personnalisés pour les charges de travail en production. N'écrivez pas votre propre ordonnanceur pour la production.
