чтение потока, созданного urllib2, никогда не восстанавливается, когда соединение было прервано
Вопрос
Пытаясь сделать одно из моих приложений на Python более устойчивым в случае прерывания соединения, я обнаружил, что вызов функции чтения http-потока, созданного urllib2, может навсегда заблокировать скрипт. Р>
Я думал, что функция чтения прекратит работу и в конечном итоге вызовет исключение, но это не похоже на случай, когда соединение было прервано во время вызова функции чтения.
Вот код, который вызовет проблему:
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"
(Если вы попробуете сценарий, вам, вероятно, потребуется несколько раз прервать соединение, прежде чем вы достигнете состояния, из которого сценарий никогда не восстанавливается)
Я наблюдал за сценарием через Winpdb и сделал скриншот состояния, из которого сценарий никогда не восстанавливается (даже если сеть снова стала доступной). Р>
Winpdb http://img10.imageshack.us/img10/6716/urllib2.jpg р>
Есть ли способ создать сценарий Python, который будет продолжать работать надежно, даже если сетевое соединение было прервано? (Я бы предпочел не делать этого внутри дополнительного потока.)
Решение
Попробуйте что-то вроде:
import socket
socket.setdefaulttimeout(5.0)
...
try:
...
except socket.timeout:
(it timed out, retry)
Другие советы
Хороший вопрос, мне было бы очень интересно найти ответ. Единственный обходной путь, о котором я могу подумать, - это использовать прием сигнала, описанный в документах python , В вашем случае это будет больше похоже на:
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