Python 2.6 urlib2 timeout issue

Parece que no puedo tener en cuenta el tiempo de espera de urllib2 . Leí, supongo, todas las publicaciones relacionadas con este tema y parece que no estoy haciendo nada malo. ¿Estoy en lo correcto? Muchas gracias por su amable ayuda.

Guión:

Necesito verificar la conectividad a Internet antes de continuar con el rest de un script. Luego escribí una función (Net_Access), que se proporciona a continuación.

  • Cuando ejecuto este código con mi interfaz LAN o Wifi conectada, y al verificar un nombre de host existente: todo está bien ya que no hay ningún error o problema, por lo tanto no hay tiempo de espera.
  • Si desconecto mi conector LAN o si comparo con un nombre de host inexistente, el valor de tiempo de espera parece ser ignorado. ¿Qué hay de malo con mi código por favor?

Alguna información:

  • Ubuntu 10.04.4 LTS (ejecutándose en una VM VirtualBox v4.2.6, el SO Host es MAC OS X Lion)
  • cat /proc/sys/kernel/osrelease: 2.6.32-42-generic
  • Python 2.6.5

Mi código:

 #!/usr/bin/env python import socket import urllib2 myhost = 'http://www.google.com' timeout = 3 socket.setdefaulttimeout(timeout) req = urllib2.Request(myhost) try: handle = urllib2.urlopen(req, timeout = timeout) except urllib2.URLError as e: socket.setdefaulttimeout(None) print ('[--- Net_Access() --- No network access') else: print ('[--- Net_Access() --- Internet Access OK') 

1) Trabajando, con el conector LAN enchufado

 $ $ time ./Net_Access [--- Net_Access() --- Internet Access OK real 0m0.223s user 0m0.060s sys 0m0.032s 

2) El tiempo de espera no funciona, con el conector de LAN desconectado

 $ time ./Net_Access [--- Net_Access() --- No network access real 1m20.235s user 0m0.048s sys 0m0.060s 

Añadido a la publicación original: resultados de la prueba (usando IP en lugar de FQDN)

Según lo sugerido por @unutbu (ver comentarios), reemplazar el FQDN en myhost con una dirección IP soluciona el problema: el tiempo de espera se toma en efecto.

Conector LAN enchufado …
$ time ./Net_Access [— Net_Access () — Internet Access OK

 real 0m0.289s user 0m0.036s sys 0m0.040s 

Conector LAN desenchufado …
$ time ./Net_Access [— Net_Access () — No hay acceso a la red

 real 0m3.082s user 0m0.052s sys 0m0.024s 

Esto es bueno, pero significa que el tiempo de espera solo se puede usar con IP y no con FQDN. Extraño…

¿Alguien encontró una manera de usar el tiempo de espera de urllib2 sin pasar a la resolución pre-DNS y pasar el IP a la función, o está usando el socket para probar la conexión y luego dispara urllib2 cuando está seguro de que puede alcanzar el objective?

Muchas gracias.

Si su problema es que la búsqueda de DNS tarda una eternidad (o simplemente demasiado tiempo) en urllib2 cuando no hay conectividad de red, entonces sí, este es un problema conocido y no hay nada que pueda hacer dentro de urllib2 para solucionarlo.

Entonces, ¿se pierde toda esperanza? Bueno, no necesariamente.

Primero, veamos lo que está pasando. En última instancia, urlopen basa en getaddrinfo , que (junto con sus parientes como gethostbyname ) es notoriamente la pieza crítica de la API de socket que no se puede ejecutar de forma asíncrona o interrumpida (y en algunas plataformas, ni siquiera es seguro para subprocesos). Si desea rastrear a través de la fuente, urllib2 difiere a httplib para crear conexiones, que llama a create_connection en el socket , que llama a socket_getaddrinfo en _socket , que en última instancia llama a la función real getaddrinfo . Este es un problema infame que afecta a todos los clientes o servidores de red escritos en todos los idiomas del mundo, y no hay una solución buena y fácil.

Una opción es usar una biblioteca diferente de nivel superior que ya haya resuelto este problema. Creo que las requests basan en urllib3 que en última instancia tiene el mismo problema, pero pycurl basa en libcurl , que, si se pycurl con c-ares , hace una búsqueda de nombres de forma asíncrona y, por lo tanto, puede pycurl .

O, por supuesto, puede usar algo como twisted o tornado o alguna otra biblioteca de red asíncrona. Pero, obviamente, reescribir todo tu código para usar un cliente HTTP twisted lugar de urllib2 no es exactamente trivial.

Otra opción es “arreglar” urllib2 por monkeypatching la biblioteca estándar. Si quieres hacer esto, hay dos pasos.

Primero, tienes que proporcionar un getaddrinfo espera. Puede hacer esto vinculando c-ares , o usando ctypes para acceder a API específicas de la plataforma como getaddrinfo_a de linux, o incluso buscar los servidores de nombres y comunicarse con ellos directamente. Pero la forma realmente simple de hacerlo es usar hilos. Si está haciendo muchos de estos, querrá usar un solo hilo o un pequeño grupo de hilos, pero para un uso a pequeña escala, simplemente gire un hilo para cada llamada. Una implementación realmente rápida y sucia (leer: mala) es:

 def getaddrinfo_async(*args): result = None t = threading.Thread(target=lambda: result=socket.getaddrinfo(*args)) t.start() t.join(timeout) if t.isAlive(): raise TimeoutError(blahblahblah) return result 

A continuación, debes obtener todas las bibliotecas que te interesan para usar esto. Dependiendo de qué tan ubicuo (y peligroso) quiera que sea su parche, puede reemplazar socket.getaddrinfo , o simplemente socket.create_connection , o simplemente el código en httplib o incluso urllib2 .

Una opción final es arreglar esto en un nivel superior. Si sus cosas de redes están sucediendo en un hilo de fondo, puede lanzar un tiempo de espera de mayor nivel en todo el asunto, y si le tomó más de un timeout de timeout determinar si se ha agotado el tiempo o no, lo sabe.

Tal vez intente esto:

 import urllib2 def get_header(url): req = urllib2.Request(url) req.get_method = lambda : 'HEAD' try: response = urllib2.urlopen(req) except urllib2.URLError: # urllib2.URLError:  return False return True url = 'http://www.kernel.org/pub/linux/kernel/v3.0/linux-3.7.1.tar.bz2' print(get_header(url)) 

Cuando desconecto mi adaptador de red, esto imprime Falso casi inmediatamente, mientras que en condiciones normales esto se imprime en Verdadero.

No estoy seguro de por qué esto funciona tan rápidamente en comparación con su código original (incluso sin necesidad de establecer el parámetro de tiempo de espera), pero quizás también le funcione a usted.


Hice un experimento esta mañana que resultó en que get_header no regresara de inmediato. Arrancé la computadora con el enrutador apagado. Luego se encendió el enrutador. Luego se habilitaron redes y conexiones inalámbricas a través de la GUI de Ubuntu Esto no pudo establecer una conexión de trabajo. En esta etapa, get_header no pudo regresar inmediatamente.

Entonces, aquí hay una solución de mayor peso que llama a get_header en un subproceso utilizando multiprocessing.Pool . El objeto devuelto por pool.apply_async tiene un método de get con un parámetro de tiempo de espera. Si no se devuelve un resultado de get_header dentro de la duración especificada por el timeout de timeout , el subproceso se termina.

Por lo tanto, check_http debe devolver un resultado en aproximadamente 1 segundo, en todas las circunstancias.

 import multiprocessing as mp import urllib2 def timeout_function(cmd, timeout = None, args = (), kwds = {}): pool = mp.Pool(processes = 1) result = pool.apply_async(cmd, args = args, kwds = kwds) try: retval = result.get(timeout = timeout) except mp.TimeoutError as err: pool.terminate() pool.join() raise else: return retval def get_header(url): req = urllib2.Request(url) req.get_method = lambda : 'HEAD' try: response = urllib2.urlopen(req) except urllib2.URLError: return False return True def check_http(url): try: response = timeout_function( get_header, args = (url, ), timeout = 1) return response except mp.TimeoutError: return False print(check_http('http://www.google.com'))