代码之家  ›  专栏  ›  技术社区  ›  davidmytton

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

  •  8
  • davidmytton  · 技术社区  · 17 年前

    笔记 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 上面说:

    解决方案是向服务器添加更多交换空间。当内核开始启动modeler或发现过程时,它首先确保交换存储上有足够的可用空间,以便在需要时启动新过程。

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

    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
    

    整个checks类可以在 http://github.com/dmytton/sd-agent/blob/82f5ff9203e54d2adeee8cfed704d09e3f00e8eb/checks.py 使用第442行定义的getProcesss函数。这由从第520行开始的doChecks()调用。

    9 回复  |  直到 9 年前
        1
  •  5
  •   pilcrow    17 年前

    您可能有一个内存泄漏,由一些 resource limit ( RLIMIT_DATA , RLIMIT_AS ?)由python脚本继承。在运行脚本之前,请检查*ulimit(1)*,并按照其他人的建议分析脚本的内存使用情况。

    ps 在你给我们看的代码片段之后? 你有提到它吗,永远不会被释放?引用 subprocess module docs :

    注: 读取的数据缓冲在内存中,因此不要使用此选项 方法,如果数据大小较大或不受限制。

    ps aux 在繁忙的系统上可能会很冗长。。。

    使现代化

    您可以使用 resource 模块:

    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脚本插入内存消耗,而无需向外部程序输出。

        2
  •  3
  •   Christopher Christopher    17 年前

    这个交换空间的答案是假的。从历史上看,Unix系统希望这样的交换空间可用,但它们不再以这种方式工作(Linux也从未以这种方式工作)。您甚至还没有接近耗尽内存,因此这不可能是实际问题-您正在耗尽其他一些有限的资源。

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

        3
  •  3
  •   Nir Alfasi    10 年前

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

        4
  •  2
  •   jmanning2k    17 年前

    如果您运行的是后台进程,那么很可能您已经重定向了进程stdin/stdout/stderr。

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

        5
  •  1
  •   S.Lott    17 年前

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

    但是您对subprocess.Popen的调用每次都会派生一个新进程。

    使现代化 .

    communicate 方法 应该 清理生成的子流程。

        6
  •  1
  •   TheJacobTaylor    17 年前

    • lsof
    • ps-aux | grep-i pname
    • 顶部

    所有人都应该提供有趣的信息。我认为这一过程占用了本应释放的资源。它是否有可能占用资源句柄(内存块、流、文件句柄、线程或进程句柄)?stdin,stdout,stderr来自生成的“ps”。内存句柄。。。从许多小的增量分配。当您的流程刚刚完成首次启动和运行,并且在24小时“坐”在那里定期启动子流程之后,我非常有兴趣看到上面的命令为您的流程显示了什么。

    因为它在几天后就死了,所以您可以让它只运行几个循环,然后每天重新启动一次,作为一种解决方法。这会在这段时间内对你有所帮助。

    雅各布

        7
  •  0
  •   Jonas Byström    17 年前

    你需要

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

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

        8
  •  0
  •   Vinay Sajip    17 年前

    我不认为您链接到的Zenoss文章中给出的情况是导致这条消息的唯一原因,因此还不清楚交换空间是否确实是问题所在。我建议,即使是在成功调用的情况下,也要记录一些更多的信息,这样您就可以在每次调用之前查看可用内存的状态 ps 呼叫

    还有一件事——如果你指定的话 shell=True 在Popen通话中,您是否看到不同的行为?

    更新: 如果不是内存,下一个可能的罪魁祸首就是文件句柄。我建议在以下情况下运行失败的命令 strace

        9
  •  0
  •   Haoran    14 年前

    虚拟内存很重要!!!

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