WritableResourceStream::handleWrite() enters infinite loop on broken TLS socket (EPIPE without PHP warning)

Abierto Apto para principiantes
#189 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
76/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Tranquilo
Stack tecnológico
php

Línea de trabajo

Comienza en el guard de handleWrite() alrededor de la línea 151 en src/WritableResourceStream.php y sigue cómo los resultados de fwrite() y las advertencias capturadas afectan al búfer de escritura y al listener. Reproduce el problema con un ReactPHP SecureServer y un cliente TLS terminado abruptamente mientras hay escrituras en cola. Se considera terminado cuando una escritura que devuelve false silenciosamente cierra el stream, mientras que una escritura con valor cero conserva el comportamiento existente dependiente de las advertencias y ya no provoca un bucle de CPU.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

On PHP 8.x with react/socket's SecureServer (TLS), when a remote client disconnects abruptly, epoll reports EPOLLOUT|EPOLLHUP on the dead socket FD. WritableResourceStream::handleWrite() calls fwrite(), which returns false because the kernel write() returns -1 EPIPE. However, PHP's OpenSSL stream wrapper does not call php_error_docref() for SSL_ERROR_SYSCALL+EPIPE errors in all code paths. The set_error_handler capture therefore gets nothing ($error === null).

The guard evaluates to false, so close() is never called. WritableResourceStream then silently passes false to substr() (implicit cast to 0 in non-strict mode), leaving the write buffer unchanged and the write listener active. epoll keeps returning EPOLLOUT|EPOLLHUP, fwrite() keeps returning false silently - 100% CPU lockup.

Reproduction: use react/socket SecureServer with TLS, kill a client with kill -9 while the server has queued writes to that client.

Fix: separate the $sent === false case (hard error - always close) from $sent === 0 (may be transient EAGAIN/WANT_WRITE - only close when PHP warning is present):

// Before
if (($sent === 0 || $sent === false) && $error !== null) {

// After
if ($sent === false || ($sent === 0 && $error !== null)) {

This issue was investigated with the help of AI, but the 100% CPU usage issue is real and after applying the above fix on production, it seems to have disappeared.

Lenguaje dominante
PHP
Estrellas
692
Forks
63
Métricas de merge de PR
Sin PR fusionados en 30 d

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de reactphp/stream

Todos los issues de reactphp/stream

Issues similares

Más issues de PHP

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.