Что мне делать, если socket.setdefaulttimeout () не работает? - PullRequest
8 голосов
/ 11 декабря 2011

Я пишу сценарий (многопоточный) для извлечения содержимого с веб-сайта, и сайт не очень стабилен, поэтому время от времени возникает http-запрос, который не может быть даже истек по времени socket.setdefaulttimeout().Поскольку я не могу контролировать этот сайт, единственное, что я могу сделать, - это улучшить свои коды, но сейчас у меня заканчиваются идеи.

Примеры кодов:

socket.setdefaulttimeout(150)

MechBrowser = mechanize.Browser()
Header = {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1 (.NET CLR 3.5.30729)'}
Url = "http://example.com"
Data = "Justatest=whatever&letstry=doit"
Request = urllib2.Request(Url, Data, Header)
Response = MechBrowser.open(Request)
Response.close()

Что я должен сделать, чтобы принудительно завершить запрос на зависание?На самом деле я хочу знать, почему socket.setdefaulttimeout(150) не работает в первую очередь.Кто-нибудь может мне помочь?

Добавлено: (и да, проблема все еще не решена)

ОК, я последовал предложению Томаша и изменил коды на MechBrowser.open(Request, timeout = 60),но то же самое происходит.До сих пор я все еще получал произвольные запросы, иногда это несколько часов, а иногда - несколько дней.Что мне теперь делать?Есть ли способ заставить эти зависающие запросы выйти?

Ответы [ 4 ]

22 голосов
/ 11 декабря 2011

Хотя socket.setsocketimeout будет устанавливать время ожидания по умолчанию для новых сокетов, если вы не используете сокеты напрямую, настройку можно легко перезаписать. В частности, если библиотека вызовет socket.setblocking на своем сокете, она сбросит время ожидания.

urllib2.open имеет аргумент времени ожидания, однако в urllib2.Request нет времени ожидания. Поскольку вы используете mechanize, вам следует обратиться к их документации:

Начиная с Python 2.6, urllib2 использует атрибут .timeout для объектов Request внутри. Однако urllib2.Request не имеет аргумента конструктора тайм-аута, и urllib2.urlopen () игнорирует этот параметр. mechanize.Request имеет аргумент конструктора времени ожидания, который используется для установки атрибута с тем же именем, а mechanize.urlopen () не игнорирует атрибут времени ожидания.

источник: http://wwwsearch.sourceforge.net/mechanize/documentation.html

--- EDIT ---

Если либо socket.setsockettimeout, либо передача тайм-аута на mechanize работает с небольшими значениями, но не с более высокими, источник проблемы может быть совершенно другим. Во-первых, ваша библиотека может открывать несколько соединений (в данном случае - @ Cédric Julien), поэтому время ожидания применяется к каждой отдельной попытке socket.open и, если она не останавливается при первом сбое, может занять до timeout * num_of_conn секунд. Другое дело socket.recv: если соединение действительно медленное и вам не повезло, весь запрос может занять до timeout * incoming_bytes, как с каждым socket.recv мы можем получить один байт, и каждый такой вызов может занять timeout секунд. Поскольку вы вряд ли пострадаете именно от этого темного сценария (один байт на секунды времени ожидания, вы должны быть очень грубым мальчиком), очень вероятно, что потребуются возрасты для очень медленных соединений и очень больших таймаутов.

Единственное решение, которое у вас есть, - принудительно установить таймаут для всего запроса, но здесь нет ничего общего с сокетами. Если вы работаете в Unix, вы можете использовать простое решение с сигналом ALARM. Вы устанавливаете сигнал на timeout секунды, и ваш запрос будет прекращен (не забудьте его поймать). Вы можете использовать оператор with, чтобы сделать его простым и понятным, например:

import signal, time

def request(arg):
  """Your http request"""
  time.sleep(2)
  return arg

class Timeout():
  """Timeout class using ALARM signal"""
  class Timeout(Exception): pass

  def __init__(self, sec):
    self.sec = sec

  def __enter__(self):
    signal.signal(signal.SIGALRM, self.raise_timeout)
    signal.alarm(self.sec)

  def __exit__(self, *args):
    signal.alarm(0) # disable alarm

  def raise_timeout(self, *args):
    raise Timeout.Timeout()

# Run block of code with timeouts
try:
  with Timeout(3):
    print request("Request 1")
  with Timeout(1):
    print request("Request 2")
except Timeout.Timeout:
  print "Timeout"

# Prints "Request 1" and "Timeout"

Если вы хотите быть более портативным, чем этот, вы должны использовать несколько более мощных пушек, например multiprocessing, так что вы вызовете процесс для вызова вашего запроса и прекратите его в случае задержки. Поскольку это будет отдельный процесс, вам придется что-то использовать для передачи результата обратно в ваше приложение, это может быть multiprocessing.Pipe. Вот пример:

from multiprocessing import Process, Pipe
import time

def request(sleep, result):
  """Your http request example"""
  time.sleep(sleep)
  return result

class TimeoutWrapper():
  """Timeout wrapper using separate process"""
  def __init__(self, func, timeout):
    self.func = func
    self.timeout = timeout

  def __call__(self, *args, **kargs):
    """Run func with timeout"""
    def pmain(pipe, func, args, kargs):
      """Function to be called in separate process"""
      result = func(*args, **kargs) # call func with passed arguments
      pipe.send(result) # send result to pipe

    parent_pipe, child_pipe = Pipe() # Pipe for retrieving result of func
    p = Process(target=pmain, args=(child_pipe, self.func, args, kargs))
    p.start()
    p.join(self.timeout) # wait for prcoess to end

    if p.is_alive():
      p.terminate() # Timeout, kill
      return None # or raise exception if None is acceptable result
    else:          
      return parent_pipe.recv() # OK, get result

print TimeoutWrapper(request, 3)(1, "OK") # prints OK
print TimeoutWrapper(request, 1)(2, "Timeout") # prints None

У вас действительно нет особого выбора, если вы хотите принудительно завершить запрос через фиксированное количество секунд. socket.timeout предоставит тайм-аут для операции с одним сокетом (connect / recv / send), но если у вас их несколько, вы можете страдать от очень длительного времени выполнения.

2 голосов
/ 23 декабря 2011

Из их документации:

Начиная с Python 2.6, urllib2 использует атрибут .timeout для объектов Request внутренне. Тем не менее, urllib2.Request не имеет конструктора тайм-аута аргумент, а urllib2.urlopen () игнорирует этот параметр. mechanize.Request имеет аргумент конструктора времени ожидания, который используется для установить атрибут с тем же именем, а mechanize.urlopen () не игнорировать атрибут timeout.

Возможно, вам следует попробовать заменить urllib2.Request на mechanize.Request.

0 голосов
/ 28 декабря 2011

Вы можете попробовать использовать механизировать с eventlet . Это не решит проблему тайм-аута, но greenlet не блокирует, поэтому может решить вашу проблему с производительностью.

0 голосов
/ 28 декабря 2011

Я предлагаю простой обходной путь - переместите запрос в другой процесс и, если он не сможет завершить, завершите его из вызывающего процесса, таким образом:

    checker = Process(target=yourFunction, args=(some_queue))
    timeout = 150
    checker.start()
    counter = 0
    while checker.is_alive() == True:
            time.sleep(1)
            counter += 1
            if counter > timeout :
                    print "Son process consumed too much run-time. Going to kill it!"
                    kill(checker.pid)
                    break

просто, быстро и эффективно.

...