sockets - Error CERRADO al establecer muchas conexiones con gen_tcp en paralelo(¿Error?)
erlang gen-tcp (1)
Cuando trato de establecer una gran cantidad de conexiones TCP en paralelo, observo un comportamiento extraño, considero un posible error en gen_tcp
.
El escenario es un servidor que escucha en un puerto con múltiples aceptadores concurrentes. Desde un cliente establezco una conexión llamando a gen_tcp:connect/3
, luego envío un mensaje "Ping" al servidor y espero en modo pasivo una respuesta "Pong". Al realizar secuencialmente las llamadas ''get_tcp: connect / 3'' todo funciona bien, incluso para una gran cantidad de conexiones (he probado hasta ~ 28000).
El problema ocurre cuando se intenta establecer muchas conexiones en paralelo (dependiendo de la máquina entre ~ 75 y varios cientos). Si bien la mayoría de las conexiones aún se establecen, algunas conexiones fallan con un error closed
en gen_tcp:recv/3
. Lo extraño es que estas conexiones no fallaron antes, las llamadas a gen_tcp:connect/3
y gen_tcp:send/2
tuvieron éxito (es decir, se devolvieron ok
). En el lado del servidor, no veo una conexión correspondiente para estas conexiones "extrañas", es decir, no se devuelve gen_tcp:accept/1
. Tengo entendido que un ''get_tcp: connect / 3'' exitoso debería dar como resultado una conexión aceptada coincidente en el lado del servidor.
Ya archivé un informe de error , allí puede encontrar una descripción más detallada y un ejemplo de código mínimo para demostrar el problema. Pude reproducir el problema en Linux y Mac OS X y con diferentes versiones de Erlang.
Mis preguntas aquí son:
- ¿Alguien puede reproducir el problema y puede confirmar que se trata de un comportamiento erróneo?
- ¿Alguna idea para una solución alternativa? ¿Cómo lidiar con este problema, otro que inicia todas las conexiones de forma secuencial (que toma una eternidad)?
TCP Client de tres vías Handshake Server
connect()│──┐ │listen()
│ └──┐ │
│ SYN │
│ └──┐ │
│ └▶│ STATE
│ ┌──│SYN-RECEIVED
│ ┌──┘ │
│ SYN-ACK │
│ ┌──┘ │
STATE │◀┘ │
ESTABLISHED│──┐ │
│ └──┐ │
│ └ACK │
│ └──┐ │ STATE
│ └▶│ESTABLISHED
▽ ▽
El problema radica en los detalles más finos del handshake de 3 vías para establecer una conexión TCP y la cola para las conexiones entrantes en el socket de escucha. Vea este excelente artículo para más detalles, gran parte de la siguiente explicación fue informada por este artículo.
En Linux, en realidad hay dos colas para las conexiones entrantes. Cuando el servidor recibe una solicitud de conexión (paquete SYN
) y realiza la transición al estado SYN-RECEIVED
, esta conexión se coloca en la cola SYN
. Si se recibe un ACK
correspondiente, las conexiones se colocan en la cola de aceptación para que la aplicación consuma. La opción {backlog, N}
(predeterminado: 5) en gen_tcp:listen/2
determina la longitud de la cola de acceso.
Cuando el servidor recibe un ACK
mientras la cola de aceptación está completa, básicamente se ignora el ACK
y no se envía ningún RST
al cliente. Hay un tiempo de espera asociado con el estado SYN-RECEIVED
: si no se recibe ACK
(o se ignora, como es el caso aquí), el servidor reenviará el SYN-ACK
. El cliente luego reenvía el ACK
. Si la aplicación consume una entrada de accept queue antes de que se haya alcanzado el número máximo de reintentos de SYN-ACK
, el servidor eventualmente procesará uno de los ACKs
duplicados y la transición al estado ESTABLISHED
. Si se ha alcanzado el número máximo de intentos, el servidor enviará un RST
al cliente para restablecer la conexión.
Volviendo al comportamiento observado al iniciar muchas conexiones en paralelo. La explicación es que la cola de aceptación en el servidor se llena más rápido de lo que nuestra aplicación consume las conexiones aceptadas. Las gen_tcp:connect/3
en el lado del cliente regresan con éxito tan pronto como reciben el primer SYN-ACK
. Las conexiones no se restablecen inmediatamente porque el servidor reintenta SYN-ACK
. El servidor no informa estas conexiones como exitosas, porque todavía están en estado SYN-RECEIVED
.
En el sistema derivado de BSD (incluido Mac OS X), la cola para las conexiones entrantes funciona un poco diferente, consulte el artículo mencionado anteriormente.