lendo um fluxo feita por urllib2 nunca se recupera quando a conexão foi interrompida
Pergunta
Ao tentar fazer uma das minhas aplicações python um pouco mais robusto em caso de interrupções de conexão eu descobri que chamar a função de leitura de um http-stream feita por urllib2 pode bloquear o script para sempre.
Eu pensei que a função de leitura será tempo limite e, eventualmente, gerar uma exceção, mas isso não costura para ser o caso quando a ligação foi interrompida durante uma chamada de função de leitura.
Aqui está o código que fará com que o problema:
import urllib2
while True:
try:
stream = urllib2.urlopen('http://www.google.de/images/nav_logo4.png')
while stream.read(): pass
print "Done"
except:
print "Error"
(Se você experimentar o script, você provavelmente precisará interromper a ligação várias vezes antes de você chegar ao estado a partir do qual o script nunca se recupera)
Eu assisti o script via Winpdb e fez uma imagem do estado do qual o script nunca mais se recuperar (mesmo se a rede tem disponível novamente).
Winpdb http://img10.imageshack.us/img10/6716/urllib2.jpg
Existe uma maneira de criar um script python que vai continuar a trabalhar mesmo confiável, se a conexão de rede foi interrompida? (Eu preferiria evitar fazer isso dentro de uma linha extra.)
Solução
Tente algo como:
import socket
socket.setdefaulttimeout(5.0)
...
try:
...
except socket.timeout:
(it timed out, retry)
Outras dicas
Boa pergunta, eu estaria realmente interessado em encontrar uma resposta. A única solução que eu poderia pensar é usando o truque sinal explicado em python docs . No seu caso, será mais parecido com:
import signal
import urllib2
def read(url):
stream = urllib2.urlopen(url)
return stream.read()
def handler(signum, frame):
raise IOError("The page is taking too long to read")
# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)
# This read() may hang indefinitely
try:
output = read('http://www.google.de/images/nav_logo4.png')
except IOError:
# try to read again or print an error
pass
signal.alarm(0) # Disable the alarm