Python subprocess.Popen错误与OSError:[Errno 12]一段时间后无法分配内存

时间:2009-08-01 15:14:31

标签: python linux memory

注意:此问题已重新询问,并附有所有调试尝试的摘要here


我有一个Python脚本作为后台进程运行,每60秒执行一次。部分原因是调用subprocess.Popen来获取ps的输出。

ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]

运行几天后,调用错误:

File "/home/admin/sd-agent/checks.py", line 436, in getProcesses
File "/usr/lib/python2.4/subprocess.py", line 533, in __init__
File "/usr/lib/python2.4/subprocess.py", line 835, in _get_handles
OSError: [Errno 12] Cannot allocate memory

然而,服务器上free的输出是:

$ free -m
                  total       used       free     shared     buffers    cached
Mem:                894        345        549          0          0          0
-/+ buffers/cache:  345        549
Swap:                 0          0          0

我一直在寻找这个问题,发现this article说:

解决方案是为服务器添加更多交换空间。当内核要求启动建模器或发现过程时,它首先确保交换存储上有足够的可用空间(如果需要的话)。

我注意到上面的免费输出没有可用的交换。这可能是问题和/或可能存在的其他解决方案吗?

更新时间:2009年8月13日上述代码每60秒调用一次,作为一系列监控功能的一部分。该进程已进行守护,并使用sched计划检查。上述功能的具体代码是:

def getProcesses(self):
    self.checksLogger.debug('getProcesses: start')

    # Memory logging (case 27152)
    if self.agentConfig['debugMode'] and sys.platform == 'linux2':
        mem = subprocess.Popen(['free', '-m'], stdout=subprocess.PIPE).communicate()[0]
        self.checksLogger.debug('getProcesses: memory before Popen - ' + str(mem))

    # Get output from ps
    try:
        self.checksLogger.debug('getProcesses: attempting Popen')

        ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]

    except Exception, e:
        import traceback
        self.checksLogger.error('getProcesses: exception = ' + traceback.format_exc())
        return False

    self.checksLogger.debug('getProcesses: Popen success, parsing')

    # Memory logging (case 27152)
    if self.agentConfig['debugMode'] and sys.platform == 'linux2':
        mem = subprocess.Popen(['free', '-m'], stdout=subprocess.PIPE).communicate()[0]
        self.checksLogger.debug('getProcesses: memory after Popen - ' + str(mem))

    # Split out each process
    processLines = ps.split('\n')

    del processLines[0] # Removes the headers
    processLines.pop() # Removes a trailing empty line

    processes = []

    self.checksLogger.debug('getProcesses: Popen success, parsing, looping')

    for line in processLines:
        line = line.split(None, 10)
        processes.append(line)

    self.checksLogger.debug('getProcesses: completed, returning')

    return processes

这是一个更大的类的一部分,称为检查,它在守护程序启动时初始化一次。

整个检查类可以在http://github.com/dmytton/sd-agent/blob/82f5ff9203e54d2adeee8cfed704d09e3f00e8eb/checks.py找到,其中getProcesses函数是从第442行定义的。这是由doChecks()从第520行开始调用的。

9 个答案:

答案 0 :(得分:5)

你的python脚本继承了一些resource limitRLIMIT_DATARLIMIT_AS?)可能会导致内存泄漏。在运行脚本之前检查你的* ulimit(1)* s,并像其他人建议的那样分析脚本的内存使用情况。

在您向我们展示的代码段后,您对变量ps做了什么?您是否保留对它的引用,永远不会被释放?引用subprocess module docs

  

注意:读取的数据缓冲在内存中,因此不要使用它   方法,如果数据大小很大或无限制。

...和 ps aux 在繁忙的系统上可能很冗长......

<强>更新

您可以使用resource模块使用python脚本检查rlimits:

import resource
print resource.getrlimit(resource.RLIMIT_DATA) # => (soft_lim, hard_lim)
print resource.getrlimit(resource.RLIMIT_AS)

如果这些返回“无限制” - (-1, -1) - 那么我的假设是不正确的,你可以继续前进!

另见resource.getrusage,尤其是ru_??rss字段,它可以帮助您使用python脚本检测内存消耗,而不会出现外部程序。

答案 1 :(得分:3)

那个交换空间的答案是虚假的。从历史上看,Unix系统需要像这样的交换空间,但它们不再以那种方式工作(并且Linux从未以这种方式工作)。你甚至都没有接近内存耗尽,所以这不太可能是实际问题 - 你的资源已经耗尽了其他一些资源。

鉴于错误发生的位置(_get_handles调用os.pipe()为子进程创建管道),您可能遇到的唯一真正问题是没有足够的免费文件描述符。我会寻找未封闭的文件(在执行popen的进程的PID上的lsof -p)。如果您的程序确实需要一次打开大量文件,则增加用户限制和/或打开文件描述符的系统限制。

答案 2 :(得分:3)

当你使用popen时,如果你想要关闭额外的文件描述符,你需要交出close_fds = True。

创建一个新的管道,它从后面的跟踪中发生在_get_handles函数中,创建了2个文件描述符,但是您当前的代码永远不会关闭它们,最终会达到系统最大fd限制。

不确定为什么您收到的错误表示内存不足:它应该是文件描述符错误,因为pipe()的返回值有此问题的错误代码。

答案 3 :(得分:2)

如果您正在运行后台进程,则可能是您已重定向进程stdin / stdout / stderr。

在这种情况下,将选项“close_fds = True”附加到您的Popen调用,这将阻止子进程继承您的重定向输出。这可能是你遇到的限制。

答案 4 :(得分:1)

您可能希望在添加交换空间之前等待所有这些PS进程完成。

完全不清楚“每60秒执行一次后台进程”意味着什么。

但是你对subprocess.Popen的调用每次都要求一个新的进程。

<强>更新

我猜你以某种方式让所有这些进程在僵尸状态下运行或挂起。但是,communicate方法应该清理生成的子进程。

答案 5 :(得分:0)

我不认为您链接的Zenoss文章中给出的情况是此消息的唯一原因,因此尚不清楚交换空间肯定是问题所在。我建议甚至在成功通话时记录更多信息,以便每次在ps呼叫之前都可以看到可用内存的状态。

还有一件事 - 如果你在Popen电话中指定shell=True,你会看到不同的行为吗?

更新:如果不是内存,下一个可能的罪魁祸首就是文件句柄。我建议在strace下运行失败的命令,以确切了解哪些系统调用失败。

答案 6 :(得分:0)

你有没有看过你的过程?

  • lsof
  • ps -aux | grep -i pname

所有人都应该提供有趣的信息。我认为这个过程正在占用应该被释放的资源。它是否有可能占用资源句柄(内存块,流,文件句柄,线程或进程句柄)? stdin,stdout,stderr来自产生的“ps”。内存句柄,......来自许多小的增量分配。我会非常感兴趣的是看到上面的命令在第一次完成启动和运行时以及在“坐”24小时后定期启动子流程时为您的流程显示的内容。

由于它在几天后死亡,你可以让它只运行几个循环,然后每天重新启动一次作为一种解决方法。那会对你有所帮助。

雅各

答案 7 :(得分:0)

你需要

ps = subprocess.Popen(["sleep", "1000"])
os.waitpid(ps.pid, 0)

免费资源。

注意:这不适用于Windows。

答案 8 :(得分:0)

虚拟内存很重要!!!

在将swap添加到我的操作系统之前,我遇到了同样的问题。虚拟内存的公式通常如下:SwapSize + 50%* PhysicalMemorySize。我最终通过添加更多物理内存或添加交换磁盘来解决此问题。 close_fds在我的情况下不起作用。