我遇到了一个问题,因为urllib2.urlopen
/ requests.post
偶尔偶尔会在socket.recv
上阻止而且永远不会返回。
我正在试图找出这种情况发生的原因并解决这个问题,但同时我想知道是否有办法防止它永远阻塞?
我已经知道timeout
和urllib2.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指出这一点!!
但是在添加了超时参数(使用timeout
和urllib2
进行测试)后,我遇到了一些非常奇怪和微妙的场景,可能是特定于mac的,其中超时不正常我越来越倾向于相信是一个错误。我将继续调查并确切地找出问题所在。再次感谢tomasz对此的帮助!
答案 0 :(得分:5)
我相信您可以通过调整操作系统级别的TCP设置来摆脱挂起状态,但假设您的应用程序无法在专用(并且可由您维护)的计算机上运行,那么您应该寻求更一般的解决方案。 / p>
你问:
是否有可能在没有数据通过套接字发送/接收一段时间后才会超时
这正是socket.settimeout
(或传递给urllib2
的那个)给你的行为。与基于SIGALRM的超时相反(即使在慢速数据传输期间也会终止),只有在定义的时间段内没有传输数据时,才会发生传递给套接字的超时。对socket.send
或socket.recv
的调用应返回部分计数,如果在此期间内已传输了一些但未传输的所有数据,urllib2
将使用后续调用以传输剩余的数据数据
如果这样说,如果在多个send
呼叫中执行POST呼叫,那么你的POST呼叫仍然会在上传的某个地方终止,而任何(但不是第一个)呼叫都会阻塞并超时而不发送任何数据。您给人的印象是,您的应用程序无法正确处理,但我认为应该这样做,因为它类似于强制终止进程或只是断开连接。
您是否经过测试并确认socket.settimeout
无法解决您的问题?或者你只是不确定行为是如何实现的?如果前者是正确的,请你提供更多细节吗?我很确定你只是设置超时是安全的,因为python只是使用低级BSD套接字实现,其行为如上所述。要为您提供更多参考,请查看setsockopt
手册页和SO_RCVTIMEO
或SO_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)",)
一切都按预期工作!如果没有将超时作为参数传递,urllib2
在socket.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