jueves, 18 de diciembre de 2014

Sockets no bloqueantes en Python

Hola a todos. El uso de sockets es sin duda uno de los recursos más utilizados que hay, y también uno de los peor utilizados.

Los sockets pueden configurarse como bloqueantes (por defecto) o hacerlos no bloqueantes mediante "fcntl" con el "flag" O_NONBLOCK. Y la diferencia es brutal.

Un socket bloqueante detendrá la ejecución del programa cuando se haga una llamada a "recv" y no haya datos para recibir. Y cuando se reciban los datos la ejecución continuará.

Mientras que un socket bloqueante, al hacer "recv" si hay datos se obtienen y si no los hay se devuelve una excepción con un valor "errno" de 11 (EAGAIN), que nos indicará básicamente un "prueba otra vez".

Pues bien, en un programa con sockets supuestamente bloqueantes vi que se comportaban como no bloqueantes sin razón aparente y he tardado 3 días en encontrar la solución.

El problema era debido al "timeout" por defecto. Me explico. En Python el "timeout" por defecto para los sockets está establecido a "None". Esto significa que si se intenta establecer una conexión con un host remoto y la conexión no se establece vaya usted a saber por qué, el programa se queda esperando indefinidamente ya que no hay un timeout.

Así, que en su día hice el cambio de establecer un "timeout" por defecto de 30 segundos. Y un tiempo después (hace 3 días), se detectó un problema derivado de esta decisión.

Y es que al establecer el timeout los sockets se vuelven no bloqueantes. Pero es más, muchos ni siquiera esperan esos 30 segundos.

¿Que cómo es esto posible? Porque no todos los sockets son del mismo tipo ni se usan para lo mismo.

Los sockets que establecen una conexión con un host remoto sí funcionan correctamente (con su timeout de 30 segundos), pero los sockets que se utilizan para la comunicación entre procesos con la librería "multiprocessing", estos funcionan como sockets no bloqueantes y no respetan el timeout de 30 segundos.

Así que la comunicación entre procesos "con multiprocessing.Pipe", y "multiprocessing.Listener" (usado dentro "multriprocessing,reduce_handle"), sencillamente no funcionaba.

Con lo que el resultado es que he tenido que quitar el timeout por defecto de 30 segundos, dejarlo como "None", y poner el timeout de 30 segundos sólo a aquellos sockets que me interesaban.

RESUMIENDO: No se puede establecer un timeout por defecto si usas comunicación entre procesos con la librería "multiprocesing"

Salu2 a to2

No hay comentarios:

Publicar un comentario