Что мне делать, если socket.setdefaulttimeout() не работает?

Я пишу сценарий (многопоточный) для извлечения содержимого с веб-сайта, и сайт не очень стабилен, поэтому время от времени возникает запрос 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 ответа

Решение

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

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

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

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

---РЕДАКТИРОВАТЬ---

Если либо 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), но если у вас их несколько, вы можете страдать от очень длительного времени выполнения.

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

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

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

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

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

    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

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

Другие вопросы по тегам