PHP 8.6: la nueva Polling API que acerca PHP a la programación asíncrona

PHP nació bloqueante. Una petición entra, se procesa de arriba a abajo y se devuelve la respuesta. Un hilo, una ejecución lineal. Eso ha funcionado durante décadas porque el modelo es simple de entender, de depurar y de escalar horizontalmente añadiendo más servidores. Pero tiene un problema serio cuando el cuello de botella es el I/O: mientras esperas a que la base de datos responda o a que un socket reciba datos, el hilo se queda parado sin hacer nada.

Para saltarse esa limitación han existido soluciones desde hace tiempo. ReactPHP te da un event loop en espacio de usuario. Swoole y OpenSwoole son extensiones en C que reescriben la ejecución de PHP desde abajo. AMPHP va por un camino parecido a ReactPHP pero apoyándose en Fibers desde PHP 8.1. Todas funcionan, pero tienen un coste: necesitas pensar de forma diferente, estructurar tu código alrededor de callbacks o corrutinas, y en muchos casos romper la compatibilidad con librerías que asumen que PHP es síncrono.

La Polling API de PHP 8.6 no pretende resolver todos esos problemas de golpe. Lo que hace es más específico: ofrece una capa estándar de bajo nivel para esperar eventos de I/O sin bloquearse, integrada en el núcleo del lenguaje. Así los frameworks y extensiones que hoy usan soluciones propias pueden converger en algo común.

Qué propone la RFC

La RFC está disponible en wiki.php.net/rfc/polling_api y merece una lectura directa si te interesa el nivel de detalle técnico. El resumen es este: se expone a PHP userland una API que internamente usa mecanismos del sistema operativo (epoll en Linux, kqueue en BSD y macOS) para saber cuándo un descriptor de fichero tiene datos listos para leer o escribir.

Las funciones principales que propone la RFC son tres:

  • polling_create(): crea un recurso de polling que actúa como contenedor de los descriptores que quieres vigilar.
  • polling_add($poll, $fd, $flags): registra un descriptor de fichero (socket, pipe...) con los eventos que te interesan, usando constantes como POLLING_READ o POLLING_WRITE.
  • polling_wait($poll, int $timeout_ms): bloquea durante como máximo $timeout_ms milisegundos y devuelve un array con los descriptores que tienen eventos listos.
  • polling_remove($poll, $fd): elimina un descriptor del grupo cuando ya no te hace falta vigilarlo.

El punto clave es que esto no es una API de alto nivel. No hay callbacks, no hay promesas, no hay sintaxis especial. Es la capa más básica posible: registras descriptores, esperas y procesas lo que esté listo. Construir algo útil encima de eso requiere más trabajo, pero también significa que es compatible con cualquier arquitectura que quieras montar.

Por qué la stdlib lo necesita aunque ya exista ext-uv

Hay quien se pregunta para qué hace falta esto cuando ya existe ext-uv, la extensión oficial que expone libuv a PHP. La respuesta es que ext-uv no va en el núcleo, hay que instalarlo por separado y no todos los entornos lo tienen disponible. La Polling API va en el core de PHP, igual que los sockets o las funciones de red. Cualquier instalación de PHP 8.6 la tendrá sin configurar nada extra. Eso cambia mucho las cosas para librerías que quieren publicar código portable.

Cómo se usa en la práctica

Este es un ejemplo de servidor TCP básico no bloqueante usando la Polling API tal como la plantea la RFC. El código es ilustrativo: asume que las funciones ya están disponibles en una build de PHP 8.6.

$server = stream_socket_server('tcp://0.0.0.0:8080', $errno, $errstr);
stream_set_blocking($server, false);

$poll = polling_create();
polling_add($poll, $server, POLLING_READ);

$clients = [];

while (true) {
    $events = polling_wait($poll, timeout_ms: 1000);

    foreach ($events as $event) {
        $fd = $event['fd'];

        if ($fd === $server) {
            // Nueva conexión entrante
            $client = stream_socket_accept($server, 0);
            if ($client) {
                stream_set_blocking($client, false);
                polling_add($poll, $client, POLLING_READ);
                $clients[(int) $client] = $client;
            }
        } else {
            // Datos de un cliente existente
            $data = fread($fd, 4096);
            if ($data === false || $data === '') {
                polling_remove($poll, $fd);
                unset($clients[(int) $fd]);
                fclose($fd);
            } else {
                // Eco de vuelta al cliente
                fwrite($fd, $data);
            }
        }
    }
}

El servidor acepta conexiones sin bloquearse, las registra en el poll y luego en cada iteración del bucle atiende a todos los que tienen datos listos. Sin hilos, sin extensiones externas, con PHP puro.

La diferencia con Fibers (PHP 8.1)

Desde PHP 8.1 tenemos Fibers, que son corrutinas cooperativas: puedes pausar la ejecución de una función en mitad de su trabajo y reanudarla más tarde. Eso resuelve el problema de la concurrencia cooperativa, pero no el del I/O no bloqueante. Una Fiber puede suspenderse, pero si dentro de esa Fiber haces una lectura de red bloqueante, el hilo entero se para igualmente.

La Polling API resuelve la otra mitad del problema: te dice cuándo el I/O está listo a nivel de kernel, sin bloquear. La combinación de las dos es lo que forma un event loop completo: usas la Polling API para saber cuándo hay datos y Fibers para gestionar qué código se ejecuta mientras esperas. Uno no sustituye al otro, se complementan.

Para ver cómo funcionan las funciones modernas de PHP que ya deberías conocer, incluyendo Fibers, echa un vistazo a ese artículo antes de seguir aquí.

Qué cambia para ti según el tipo de proyecto

Si usas Laravel, Symfony o un framework clásico

Casi nada de momento. El modelo petición-respuesta sigue siendo el mismo y tu código no necesita tocar la Polling API directamente. Con el tiempo puede que los ORMs o los clientes HTTP de estos frameworks la usen por debajo para mejorar el rendimiento, pero eso llegará gradualmente y de forma transparente.

Si mantienes extensiones o librerías de red

Aquí sí importa. Si tienes una librería que abre sockets o se conecta a servicios externos, la Polling API te da la posibilidad de hacerlo de forma no bloqueante sin depender de ReactPHP ni de Swoole. Puedes publicar código que funcione en cualquier PHP 8.6 sin requisitos adicionales.

Si usas ReactPHP, AMPHP o similar

Estas librerías tienen sus propios mecanismos para detectar eventos de I/O, que varían según el sistema operativo y las extensiones disponibles. Con la Polling API disponible en el core, lo más probable es que actualicen sus backends para usarla cuando esté disponible, lo que mejorará la portabilidad y simplificará su código interno. No vas a notar un cambio drástico en la API pública, pero el funcionamiento interno será más limpio.

Si trabajas con Octane o RoadRunner

Estos servidores de larga duración ya gestionan la concurrencia a su manera, normalmente con Swoole o con workers separados. La Polling API puede ayudar en versiones futuras a implementar funcionalidades específicas sin necesitar Swoole, pero no esperes cambios inmediatos. Para entender mejor el rendimiento real de PHP a escala, el artículo enlazado da contexto útil sobre cómo funcionan estos entornos en producción.

Comparación con Node.js

Node.js tiene event loop desde el primer día. Su diseño entero gira en torno a él: no hay forma de escribir código Node síncrono que bloquee el hilo principal sin que sea un error de concepto. PHP va en dirección contraria: empezó síncrono y está añadiendo capas de asincronía de forma incremental, sin romper lo que ya funciona.

Ninguno de los dos enfoques es mejor en abstracto. Node apostó desde el principio por un modelo que hace fácil el I/O no bloqueante pero complica ciertos patrones síncronos. PHP apostó por la simplicidad del modelo petición-respuesta, que es muy fácil de razonar y de escalar, pero que necesita trabajo extra cuando el cuello de botella es el I/O concurrente. La Polling API es PHP cerrando esa brecha sin tirar lo que ya tiene.

Estado actual y cómo seguirlo

La RFC está en fase de discusión para PHP 8.6. PHP 8.6 se espera a finales de 2026 según el calendario habitual de releases (una versión mayor por año, en noviembre o diciembre). Las fechas pueden cambiar y el alcance de la RFC también puede ajustarse durante el proceso de votación.

Para seguir el estado en tiempo real tienes dos sitios principales:

  • La propia RFC en wiki.php.net/rfc/polling_api, que se actualiza con los resultados de votación y los cambios de diseño.
  • El repositorio oficial de PHP en GitHub (github.com/php/php-src), donde puedes seguir los PRs y compilar una build experimental desde la rama de desarrollo si quieres probarlo antes del release.

Si quieres jugar con una build experimental ahora, la forma más rápida es clonar php-src, buscar la rama donde se está implementando la RFC y compilar localmente. No es para producción, pero da una idea bastante clara de cómo quedará la API.

Imagen: Pexels / Markus Spiske

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP