3

我遇到了一个问题,urllib2.urlopen/requests.post偶尔会永远阻塞socket.recv并且永远不会返回。

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

我已经知道timeoutfor 的可选参数urllib2.urlopensocket.setdefaulttimeout但不幸的是,对于我的用例来说,超时不是解决方案,因为我正在使用 POST 上传文件,我使用的任何超时值都会冒中断正常文件上传的风险。

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

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

如果有使用 select / poll 的解决方案,我将如何将其合并到urllib2.urlopen/中requests.post


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

更新 似乎我一直对timeoutpython中的含义有误解,它似乎实际上是空闲超时或读/写超时(可能是我第一次不同意 Guido)。我一直认为这是响应应该返回的最长时间 - 谢谢@tomasz 指出这一点!

但是在添加超时参数(同时使用urllib2和测试requests)之后,我遇到了一些非常奇怪和微妙的场景,可能是特定于 mac 的,其中超时无法正常工作,我越来越倾向于认为这是一个错误。我将继续调查并找出问题所在。再次感谢 tomasz 对此的帮助!

4

3 回答 3

6

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

您询问:

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

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

话虽如此,如果您的 POST 调用将在多个send调用中执行,并且任何(但不是第一个)调用会阻塞并超时而不发送任何数据,则它仍可能在上传过程中的某个地方终止。您给人的印象是您的应用程序无法正确处理它,但我认为它应该,因为它类似于强制终止进程或只是断开连接。

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

--- EDIT --- (提供一些测试代码)

因此,我能够获取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)",)

一切都按预期工作!如果没有将超时作为参数传递,则对 的urllib2反应也很好socket.setdefaulttimeout,但Requests没有。这并不奇怪,因为内部实现根本不需要使用默认值,并且可以根据传递的参数简单地覆盖它或使用非阻塞套接字。

我一直在使用以下方法运行它:

OSX 10.8.3
Python 2.7.2
Requests 1.1.0
于 2013-03-23T22:07:51.617 回答
1

您提到无限期阻塞“非常偶尔”发生,并且您正在寻找后备以避免在这种情况下上传文件失败。在这种情况下,我建议对您的发帖调用使用超时,并在超时的情况下重试发帖。所有这些都需要一个简单的 for 循环,如果除了超时之外发生任何事情,就会中断。

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

于 2013-03-25T09:11:05.337 回答
0

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

于 2013-03-18T17:19:19.603 回答