![从网络连接读取时进程无限期挂起](https://linux22.com/image/636705/%E4%BB%8E%E7%BD%91%E7%BB%9C%E8%BF%9E%E6%8E%A5%E8%AF%BB%E5%8F%96%E6%97%B6%E8%BF%9B%E7%A8%8B%E6%97%A0%E9%99%90%E6%9C%9F%E6%8C%82%E8%B5%B7.png)
以下内容更新:
我在不同的数据中心的 Debian 虚拟机上,在不相关的脚本上遇到了类似的问题。
这看起来很像所描述的问题这里(和问这个问题的人一样,我没有在服务器前配置代理)。
与以下描述的主要区别在于,当我附加到挂起进程时,我看到的是调用recvfrom
而不是read
:
$ strace -p 17527
Process 17527 attached - interrupt to quit
recvfrom(3,
然而 Python 并没有感觉到它被代理了:
>>> import os; print os.getenv("HTTP_PROXY"), os.getenv("http_proxy")
None, None
所以我仍然很困惑。遗憾的是,链接的问题也没有最终答案。
(我也想知道这个问题是相关的,但是 S3 似乎不太可能不遵守Connection: close
标题。)
我有几个 Debian(Wheezy,x86_64)服务器,都表现出以下行为:
所有服务器都有一组 cron 作业,用于从 S3 中提取数据等。这些作业通常运行良好,但偶尔ps aux
会发现一些几小时或几天前启动的作业仍在运行,并且没有干净地完成。
使用以下命令检查它们strace -p <pid>
,在所有情况下,进程都挂在读取命令上。例如,我刚才检查的一个进程的输出是:
$ strace -p 12089
Process 12089 attached - interrupt to quit
read(5,
检查打开的文件描述符得到以下信息:
$ sudo lsof -i | grep 12089
python 12089 user 5u IPv4 809917771 0t0 TCP my.server.net:35427->185-201.amazon.com:https (ESTABLISHED)
起初我以为这只是由于 Python 脚本中没有设置读取超时,但事实似乎并非如此,原因如下:
- 当在我们的 OS X 机器(全部 10.5,i386)上使用相同的代码运行相同的作业时,不会发生这种情况。
- 脚本的一个变体做设置超时(60秒,使用
socket.setdefaulttimeout
——这是在Python 2.7中,但代码库必须与2.5兼容)从昨天开始就一直挂起。 - 另一个非 Python 进程似乎偶尔会表现出类似的行为。在本例中,Python 脚本正在执行一个
svn up --non-interactive
进程(使用subprocess.Popen
,仅供参考)。
SVN 进程的情况类似——
Python 正在等待 SVN:
$ strace -p 28034
Process 28034 attached - interrupt to quit
wait4(28127,
SVN 正在等待read
调用完成:
$ strace -p 28127
Process 28127 attached - interrupt to quit
read(6,
该读取指向另一个外部主机:
$ sudo lsof -i | grep 28127
svn 28127 user 3u IPv4 701186417 0t0 TCP my.server.net:49299->sparrow.telecommunity.com:svn (ESTABLISHED)
svn 28127 user 6u IPv4 701186439 0t0 TCP my.server.net:49309->sparrow.telecommunity.com:svn (ESTABLISHED)
(似乎在正在更新的目录中svn:externals
设置了一个属性;根据他们的网站,我认为这会重定向到 telecommunity.com)ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
其他可能相关的观点:
- Mac 上的 Python 环境是 2.5。在 Debian 机器上,它是 2.7。
- 我对 SVN 不是很熟悉,也不知道它挂起的原因是否本质上是同一件事。我也不完全确定这意味着什么
svn:externals
;这是在我之前设置的。 - Python 脚本本身正在从 Amazon S3 检索较大的(在某些情况下约为 10MB)数据块,这往往很慢(我看到下载时间长达三分钟,这似乎与服务器(即使位于不同的数据中心)相互通信所需的时间相比很长)。同样,我们的一些 SVN 存储库相当大。所以这基本上意味着其中一些操作是长时间运行的反正,但在某些情况下它们似乎也会挂起几个小时或几天。
- 在一台服务器上,OOM 杀手今早搞垮了 MySQL。仔细检查后发现,内存使用率为 90%,交换使用率为 100%(Monit 报告);杀死大量挂起的 Python 作业后,这些统计数据分别降至 60% 和 40%。这给我的印象是,至少有部分(如果不是全部)数据正在下载/读取(并在进程挂起时保存在内存中)。
- 这些 cron 作业正在从 S3 请求资源列表,并相应地更新 MySQL 表列表。每个作业都以相同的列表启动,因此将尝试请求相同的资源并更新相同的表。
我能够捕获某个挂起进程的一些流量;这对我来说有点难以理解,但我想知道这是否表明连接处于活动状态且正在运行,只是速度非常非常慢?我将其作为要点提供,以避免混乱(我应该注意,这大约需要两个小时的捕获时间):https://gist.github.com/petronius/286484766ad8de4fe20b我认为这是转移注意力的花招。该端口上有活动,但与到 S3 的连接不同 —— 只是其他随机服务器活动。- 我尝试在另一个数据中心的机器上重现此问题(运行相同版本 Debian 且具有相同系统设置的虚拟机),但没有成功(我原以为问题可能与这个,但遇到这些问题的盒子不是虚拟机,并且根据
ifconfig
)。我猜这表明存在网络配置问题,但我不确定从哪里开始。
所以我想我的问题是:
- 我可以在系统级别修复这个问题吗,或者这是每个单独的进程出了问题?
read
我需要了解OS X 和 Linux 处理调用的方式是否存在根本区别,以避免无限挂起的过程?
答案1
我可以在系统级别修复这个问题吗,或者这是每个单独的进程出了问题?
很难说,因为不知道协议层面发生了什么。基本上,read(2)
将无限期地阻止:-
- TCP 连接保持打开状态。
- 您期望至少有 1 个字节的数据到达。
- 发送方尚未准备好向您发送数据。
现在,可能是进程出了问题,比如另一端在发送更多数据之前先等待你的响应,或者另一端先前的响应预期 SVN 会执行某些操作别的在请求更多数据之前。例如,假设返回了一个错误响应,这应该迫使客户端重新发送一些信息。
您无法优雅地修复此问题,因为您无法根据所掌握的信息确定此数据的发送者希望您做什么。不过,有几种方法可以避免此问题并报告它。
- 不要使用
wait
简单的阻塞模式,wait
而是在父进程中运行并配置警报。现在,当进程未能在固定时间内完成时,您可以终止它并报告发生的情况。一种便宜的方法是修改 subprocess.Popen 来调用命令timeout
。 - 修改读取,使其设置读取超时套接字选项。您可以通过修改代码来做到这一点,或者 - 使用插入器覆盖默认
socket
系统调用,以便也为接收器添加超时。这两者都不是一件容易的事。这可能会导致svn
以意想不到的方式行事。
OS X 和 Linux 处理读取调用的方式是否存在根本的不同,我需要知道这些不同才能避免无限挂起的过程?
我不知道这个问题的答案,但是如果两者的行为都是正确的,那么它们的行为应该相同。如果您尝试从尚未准备好向您发送数据的套接字读取数据,则无限期地阻塞流是预期的行为。
总的来说,我认为你最好的攻击选择是期望你的svn
命令在特定时间段内完成。如果它没有杀死它并报告你这样做了。
答案2
我想我已经解决了上面描述的问题,并且大部分谜团源于我对服务器上发生的事情的误解。
存在以下基本问题:
- 应该设置超时的 Python 脚本(我假设已经设置了)却没有设置。有些脚本在连接到 S3 时会无限期挂起,表现出无限期等待读取完成的行为。仔细检查代码并确保全局套接字超时已设置且未取消设置,似乎解决了这一问题。
- 一些旧的 Python 进程似乎被挂起了,但仔细检查后(一旦真正被阻塞的进程被清除),就会发现它们只是列出大型 S3 存储桶来检查这些存储桶中键的状态,而这个操作需要几个小时或几天才能完成。
- 在更新包含大量文件且目录结构非常深的大型项目时,SVN checkout 命令会(现在仍然)长时间挂起。客户端正在等待读取完成,但这是完全合法的(存储库服务器似乎需要很长时间才能收集需要发送回客户端的数据)。
我在这里留下这个答案来解释发生了什么,但我会接受马修的答案,因为他对实际可能出现的问题的看法是正确的。