python subprocess.Popen错误与OSError:一段时间后不能分配内存

注意 :这个问题已经被重新提出,并在这里对所有的debugging尝试进行了总结。


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

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

运行了几天之后,通话出现错误:

在getProcesses中的文件“/home/admin/sd-agent/checks.py”,行436
在__init__文件“/usr/lib/python2.4/subprocess.py”,第533行
在_get_handles文件“/usr/lib/python2.4/subprocess.py”,第835行
 OSError:[Errno 12]无法分配内存

但是,服务器上的免费输出是:

 $ free -m
                  caching总共使用的空闲共享缓冲区
 Mem:894 345 549 0 0 0
 -  / + buffers / cache:345 549
交换:0 0 0

我search了这个问题,发现这篇文章说:

解决scheme是添加更多的交换空间到您的服务器。 当内核分叉以启动build模器或发现过程时,它首先确保在交换存储器上有足够的可用空间,如果需要,新的过程。

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

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

 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中find整个检查类,使用从第442行定义的getProcesses函数。这由doChecks()从520行开始调用。

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

创建一个新的管道,从后面的trace中出现在_get_handles函数中,创建2个文件描述符,但是你当前的代码永远不会关闭它们,并最终达到你的系统的最大限制。

不知道为什么你收到的错误表明内存不足的情况:它应该是一个文件描述符错误,因为pipe()的返回值有这个问题的错误代码。

你可能会遇到由你的python脚本继承的资源限制 ( RLIMIT_DATARLIMIT_AS ?) 限制的内存泄漏。 正如其他人所建议的那样,在运行脚本之前检查* ulimit(1)* s,并分析脚本的内存使用情况。

在你给我们展示的代码片段之后,你如何处理变量ps 你有没有参考它,永远不会被释放? 引用subprocess模块文档 :

注意:读取的数据缓冲在内存中,所以如果数据量很大或者没有限制,不要使用这个方法。

…和ps aux可以在繁忙的系统上冗长…

更新

您可以使用资源模块检查您的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脚本中消耗内存,而不会将其释放到外部程序中。

交换空间的答案是假的。 从历史上看,Unix系统希望能够像这样交换空间,但是它们不再那样工作(而且Linux从来没有这样工作过)。 你甚至没有用完内存,所以这可能不是真正的问题 – 你正在耗尽其他一些有限的资源。

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

如果你正在运行一个后台进程,很可能你已经重定向了你的进程stdin / stdout / stderr。

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

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

“每60秒执行一次后台进程”的含义并不十分清楚。

但是你对子进程的调用.Popen每次都要创造一个新的进程。

更新

我猜你是不知何故将所有这些进程运行或挂在僵尸状态。 但是, communicate方法应该清理产生的子流程。

你有没有看过你的过程?

  • lsof的
  • ps -aux | grep -i pname
  • 最佳

所有应该给有趣的信息。 我在想这个过程正在捆绑应该被释放的资源。 是否有机会捆绑资源句柄(内存块,流,文件句柄,线程或进程句柄)? stdin,stdout,stderr从产生的“ps”。 内存句柄,…来自许多小的增量分配。 我会非常感兴趣的是,在第一次完成启动和运行时以及在“坐”那里定期启动子进程24小时后,看到上面的命令对于您的进程显示了什么。

由于它会在几天后死亡,所以可能只运行几个循环,然后每天重新启动一次,作为解决方法。 这将帮助你在此期间。

雅各

你需要

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

释放资源。

注意:这在Windows上不起作用。

我不认为你链接到的Zenoss文章给出的情况是这个消息的唯一原因,所以目前还不清楚交换空间是否是问题。 我建议在成功调用的时候记录更多的信息,以便在ps调用之前每次都能看到空闲内存的状态。

还有一件事 – 如果你在Popen调用中指定了shell=True ,你会看到不同的行为吗?

更新:如果不是内存,下一个可能的罪魁祸首确实是文件句柄。 我会建议在strace下运行失败的命令,看看究竟是哪个系统调用失败。

虚拟内存很重要!

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