如何在urlopen上永远阻止阻塞

时间:2013-03-18 16:55:22

标签: python sockets python-2.7 urllib2

我遇到了一个问题,因为urllib2.urlopen / requests.post偶尔偶尔会在socket.recv上阻止而且永远不会返回。

我正在试图找出这种情况发生的原因并解决这个问题,但同时我想知道是否有办法防止它永远阻塞?

我已经知道timeouturllib2.urlopen的{​​{1}}可选参数但不幸的是,对于我的用例,超时不是解决方案,因为我正在使用POST上传文件任何超时我使用的值可能会中断正常的文件上传。

我也看到了一些使用信号的解决方案,但是这会产生与使用超时相同的问题(也是问题,因为我不是从主线程中执行此操作)。

只有在一段时间内没有通过套接字发送/接收数据时,是否可以超时?或者也许我可以使用select / poll来防止我遇到的死锁/阻塞?

如果有使用select / poll的解决方案,我该如何将其纳入socket.setdefaulttimeout / urllib2.urlopen


我还有一个想法,如果我可以通过写入类型的接口发送文件数据,所以我控制迭代文件并一次发送块我可能有足够的控制来避免停顿。我不知道如何实现它,所以我问了一个问题:Upload a file with a file.write interface

更新 我似乎总是误解了python中requests.post的含义,它似乎实际上是空闲超时或读/写超时(可能是我第一次disagreed with Guido)。我一直以为这是响应应该返回的最长时间 - 谢谢@tomasz指出这一点!!

但是在添加了超时参数(使用timeouturllib2进行测试)后,我遇到了一些非常奇怪和微妙的场景,可能是特定于mac的,其中超时不正常我越来越倾向于相信是一个错误。我将继续调查并确切地找出问题所在。再次感谢tomasz对此的帮助!

3 个答案:

答案 0 :(得分:5)

我相信您可以通过调整操作系统级别的TCP设置来摆脱挂起状态,但假设您的应用程序无法在专用(并且可由您维护)的计算机上运行,​​那么您应该寻求更一般的解决方案。 / p>

你问:

  

是否有可能在没有数据通过套接字发送/接收一段时间后才会超时

这正是socket.settimeout(或传递给urllib2的那个)给你的行为。与基于SIGALRM的超时相反(即使在慢速数据传输期间也会终止),只有在定义的时间段内没有传输数据时,才会发生传递给套接字的超时。对socket.sendsocket.recv的调用应返回部分计数,如果在此期间内已传输了一些但未传输的所有数据,urllib2将使用后续调用以传输剩余的数据数据

如果这样说,如果在多个send呼叫中执行POST呼叫,那么你的POST呼叫仍然会在上传的某个地方终止,而任何(但不是第一个)呼叫都会阻塞并超时而不发送任何数据。您给人的印象是,您的应用程序无法正确处理,但我认为应该这样做,因为它类似于强制终止进程或只是断开连接。

您是否经过测试并确认socket.settimeout无法解决您的问题?或者你只是不确定行为是如何实现的?如果前者是正确的,请你提供更多细节吗?我很确定你只是设置超时是安全的,因为python只是使用低级BSD套接字实现,其行为如上所述。要为您提供更多参考,请查看setsockopt手册页和SO_RCVTIMEOSO_SNDTIMEO选项。我希望socket.settimeout能够正确使用这些函数和选项。

---编辑--- (提供一些测试代码)

所以我能够获得Requests模块并测试行为以及urllib2。我运行的服务器正在接收每个recv呼叫之间间隔越来越大的数据块。正如预期的那样,当间隔达到指定的超时时,客户端超时。例如:

服务器

import socket
import time

listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.bind(("localhost", 12346))
listener.listen(1)
sock,_ = listener.accept()

interval = 0.5
while 1:
  interval += 1 # increase interval by 1 second
  time.sleep(interval)
  # Get 1MB but will be really limited by the buffer
  data = sock.recv(1000000)
  print interval, len(data)
  if not data:
    break

客户 (请求模块)

import requests

data = "x"*100000000 # 100MB beefy chunk
requests.post("http://localhost:12346", data=data, timeout=4)

客户 (urllib2模块)

import urllib2

data = "x"*100000000 # 100MB beefy chunk
urllib2.urlopen("http://localhost:12346", data=data, timeout=4)

输出 (服务器)

> 1.5 522832
> 2.5 645816
> 3.5 646180
> 4.5 637832 <--- Here the client dies (4.5 seconds without data transfer)
> 5.5 294444
> 6.5 0

两位客户都提出了例外:

# urllib2
URLError: timeout('timed out',)

# Requests
Timeout: TimeoutError("HTTPConnectionPool(host='localhost', port=12346): Request timed out. (timeout=4)",)

一切都按预期工作!如果没有将超时作为参数传递,urllib2socket.setdefaulttimeout上反应良好,但Requests没有。这并不奇怪,因为内部实现根本不需要使用默认值,只需根据传递的参数覆盖它或使用非阻塞套接字。

我一直在使用以下内容运行:

OSX 10.8.3
Python 2.7.2
Requests 1.1.0

答案 1 :(得分:1)

您提到无限期阻止“非常偶然”发生,并且您正在寻找回退以避免在发生这种情况时文件上传失败。在这种情况下,我建议您使用超时进行发布呼叫,并在超时的情况下重试帖子。所有这些都需要一个简单的for循环,如果发生任何事情而不是超时,则会中断。

当然,发生这种情况时,您应该记录一条警告消息,并监控这种情况发生的频率。你应该试着找到冻结的根本原因(正如你提到的那样)。

答案 2 :(得分:0)

可能的决定之一 - 您可以将urllib2请求嵌套到具有ALRM信号处理的块,或者将其置于线程中并在超时时强制停止。 这将强制停止您的请求,尽管有任何内部urllib2问题,关于这种情况的老问题: Python: kill or terminate subprocess when timeout