从网络连接读取时进程无限期挂起

从网络连接读取时进程无限期挂起

以下内容更新:

我在不同的数据中心的 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 脚本中没有设置读取超时,但事实似乎并非如此,原因如下:

  1. 当在我们的 OS X 机器(全部 10.5,i386)上使用相同的代码运行相同的作业时,不会发生这种情况。
  2. 脚本的一个变体设置超时(60秒,使用socket.setdefaulttimeout——这是在Python 2.7中,但代码库必须与2.5兼容)从昨天开始就一直挂起。
  3. 另一个非 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 命令会(现在仍然)长时间挂起。客户端正在等待读取完成,但这是完全合法的(存储库服务器似乎需要很长时间才能收集需要发送回客户端的数据)。

我在这里留下这个答案来解释发生了什么,但我会接受马修的答案,因为他对实际可能出现的问题的看法是正确的。

相关内容