sockets - En Luasocket, ¿bajo qué condiciones puede un bloque de llamadas aceptar incluso después de seleccionar decir que es seguro de leer?
(2)
Se supone que la función de selección Luasocket indica cuándo se puede leer un socket sin bloquearlo. Al parecer, también se puede usar para indicar cuándo un servidor está listo para aceptar una nueva conexión, sin embargo, la documentación da la siguiente advertencia:
Otra nota importante: llamar a seleccionar con un socket de servidor en el parámetro de recepción antes de que una llamada a aceptar no garantice que accept se devolverá de inmediato. Use el método settimeout o accept puede bloquear para siempre.
¿Bajo qué circunstancias puede aceptar bloquearse incluso cuando se seleccionó que fue seguro para leer? ¿Hay alguna manera de forzar este problema para fines de prueba?
No sé de dónde sacaron esa idea. Nunca lo he visto en más de 20 años de programación en red.
Por supuesto, puede suceder si tiene múltiples hilos de selección (), pero esperaría que el documento lo dijera si eso era lo que se pretendía.
Esto se resume en la Sección 16.6 (Aceptación no bloqueante) de la tercera edición de la "Programación de red Unix" de W. Richard Stevens, página 461-463. UNP es probablemente el mejor libro de texto disponible para escribir códigos de red.
Aunque puede pensar que accept
no puede bloquear después de que select
indique que un socket de escucha está listo, Stevens describe una condición de carrera en algunas implementaciones de la pila de red que puede causar que se bloquee indefinidamente. (Una nota al pie atribuye la descripción a "A.Gierth"). El problema se describe por medio de un cliente de eco que:
Se conecta al servidor;
Establece la opción de socket
SO_LINGER
en el socket conectado;Inmediatamente cierra el zócalo. Como se ha configurado la opción
SO_LINGER
, al cerrar el socket se envía unRST
(reset).
Ahora, supongamos que el servidor se está ejecutando, pero en una máquina muy cargada. El cliente de eco modificado se ejecuta. La conexión TCP hace que la llamada de select
regrese con una indicación de que hay una conexión disponible. (Recuerde que la conexión fue realmente aceptada por el kernel y puesta en la cola de accept
; accept
no necesita ser ejecutada para que esto suceda).
Sin embargo, el código del servidor es interrumpido por un cambio de proceso antes de que se ejecute la llamada de accept
, y mientras tanto, el cliente logra terminar los pasos (2) y (3). Luego, el kernel recibe el restablecimiento del cliente, y ahora la conexión ya no es válida. Por lo tanto, podría eliminarlo de la cola de aceptación.
Entonces, cuando el código del servidor llega a accept
la conexión, no hay conexión para aceptar, y los bloques de llamadas accept
hasta la siguiente conexión, si hay una.
El comportamiento descrito anteriormente podría no suceder realmente. POSIX desea que la llamada de accept
falle con ECONNABORTED
incluso si hay otra conexión disponible en la cola de aceptación (que también debe recordar para tratar). De acuerdo con Stevens:
En la Sección 5.11, observamos que cuando el cliente aborta la conexión antes de que el servidor llame a `aceptar`, las implementaciones derivadas de Berkeley no devuelven la conexión abortada al servidor, mientras que otras implementaciones deben devolver` ECONNABORTED` pero a menudo devuelven `EPROTO` .
El código fuente de Stevens está disponible aquí, en el sitio del editor ; el cliente modificado es nonblock/tcpcli03.c
, y la modificación al servidor simplemente consiste en dormir durante cinco segundos antes de llamar a accept
. Entonces puedes probarlo en cualquier sistema que tengas disponible.
No creo que ni FreeBSD ni Linux exhiban el comportamiento derivado de Berkeley, aunque estoy bastante seguro de recordar que esto sucede en FreeBSD (eso podría haber sido hace más de una década, y ya no tengo una caja de FreeBSD a mano para probarlo). OpenBSD parece haber sido parcheado en 1999 para solucionar el problema (ver parche a 2.4 ); probablemente los otros derivados de Berkeley hicieron cambios similares más adelante. No tengo idea sobre MacOSX (aunque probablemente sea lo mismo que FreeBSD) o Windows. Bien podría ser que ningún sistema moderno exhibe el comportamiento, aunque seguramente fue observable cuando Stevens escribió UNP.
En cualquier caso, el consejo de Stevens es bastante simple, y nunca está de más tener cuidado. Lo que él sugiere es:
Siempre configure un socket de escucha para que no sea bloqueado cuando use
select
;Si
accept
falla conEWOULDBLOCK
,ECONNABORTED
,EPROTO
oEINTR
, ignore el error y vuelva al ciclo deselect
.