Python subprocess.Popen“OSError:无法分配内存”

注意:这个问题最初是在这里问的,但即使可以接受的答案没有被发现,赏金时间已经过期。 我正在重复问这个问题,包括原始问题中提供的所有细节。

python脚本使用sched模块每60秒运行一组类function:

# sc is a sched.scheduler instance sc.enter(60, 1, self.doChecks, (sc, False)) 

该脚本作为使用此处的代码的守护程序进程运行。

许多作为doChecks一部分而调用的类方法使用subprocess模块来调用系统函数来获取系统统计信息:

 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 -m的输出是:

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

该服务器正在运行CentOS 5.3。 我无法在自己的CentOS盒子上复制,也没有任何其他用户报告相同的问题。

我已经尝试了很多东西来debugging这个原来的问题build议:

  1. 在Popen调用之前和之后loggingfree -m的输出。 内存使用量没有明显的变化,即脚本运行时内存不会逐渐耗尽。

  2. 我给Popen调用添加了close_fds = True,但是这并没有什么区别 – 脚本仍然崩溃,同样的错误。 build议在这里和这里 。

  3. 我检查了在RLIMIT_DATA和RLIMIT_AS上显示(-1,-1)的rlimits,如此处所示。

  4. 一篇文章提出,没有交换空间可能是原因,但交换实际上是按需提供(根据networking主机),这也被认为是一个虚假的原因。

  5. 进程正在closures,因为这是使用.communicate()的行为,由Python源代码和备注在这里备份。

整个检查可以在GitHub上find,这里使用从第442行定义的getProcesses函数。这由doChecks()从520行开始。

在崩溃之前,脚本使用strace运行,输出如下:

 recv(4, "Total Accesses: 516662\nTotal kBy"..., 234, 0) = 234 gettimeofday({1250893252, 887805}, NULL) = 0 write(3, "2009-08-21 17:20:52,887 - checks"..., 91) = 91 gettimeofday({1250893252, 888362}, NULL) = 0 write(3, "2009-08-21 17:20:52,888 - checks"..., 74) = 74 gettimeofday({1250893252, 888897}, NULL) = 0 write(3, "2009-08-21 17:20:52,888 - checks"..., 67) = 67 gettimeofday({1250893252, 889184}, NULL) = 0 write(3, "2009-08-21 17:20:52,889 - checks"..., 81) = 81 close(4) = 0 gettimeofday({1250893252, 889591}, NULL) = 0 write(3, "2009-08-21 17:20:52,889 - checks"..., 63) = 63 pipe([4, 5]) = 0 pipe([6, 7]) = 0 fcntl64(7, F_GETFD) = 0 fcntl64(7, F_SETFD, FD_CLOEXEC) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory) write(2, "Traceback (most recent call last"..., 35) = 35 open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) write(2, " File \"/usr/bin/sd-agent/agent."..., 52) = 52 open("/home/admin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/bin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python24.zip/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/plat-linux2/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python2.4/lib-tk/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/lib-dynload/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/site-packages/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) write(2, " File \"/home/admin/sd-agent/dae"..., 60) = 60 open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) write(2, " File \"/usr/bin/sd-agent/agent."..., 54) = 54 open("/usr/lib/python2.4/sched.py", O_RDONLY|O_LARGEFILE) = 8 write(2, " File \"/usr/lib/python2.4/sched"..., 55) = 55 fstat64(8, {st_mode=S_IFREG|0644, st_size=4054, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000 read(8, "\"\"\"A generally useful event sche"..., 4096) = 4054 write(2, " ", 4) = 4 write(2, "void = action(*argument)\n", 25) = 25 close(8) = 0 munmap(0xb7d28000, 4096) = 0 open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) write(2, " File \"/usr/bin/sd-agent/checks"..., 60) = 60 open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory) open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory) write(2, " File \"/usr/bin/sd-agent/checks"..., 64) = 64 open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8 write(2, " File \"/usr/lib/python2.4/subpr"..., 65) = 65 fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000 read(8, "# subprocess - Subprocesses with"..., 4096) = 4096 read(8, "lso, the newlines attribute of t"..., 4096) = 4096 read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096 read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096 read(8, " p2cread\n # c2pread <-"..., 4096) = 4096 write(2, " ", 4) = 4 write(2, "errread, errwrite)\n", 19) = 19 close(8) = 0 munmap(0xb7d28000, 4096) = 0 open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8 write(2, " File \"/usr/lib/python2.4/subpr"..., 71) = 71 fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000 read(8, "# subprocess - Subprocesses with"..., 4096) = 4096 read(8, "lso, the newlines attribute of t"..., 4096) = 4096 read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096 read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096 read(8, " p2cread\n # c2pread <-"..., 4096) = 4096 read(8, "table(self, handle):\n "..., 4096) = 4096 read(8, "rrno using _sys_errlist (or siml"..., 4096) = 4096 read(8, " p2cwrite = None, None\n "..., 4096) = 4096 write(2, " ", 4) = 4 write(2, "self.pid = os.fork()\n", 21) = 21 close(8) = 0 munmap(0xb7d28000, 4096) = 0 write(2, "OSError", 7) = 7 write(2, ": ", 2) = 2 write(2, "[Errno 12] Cannot allocate memor"..., 33) = 33 write(2, "\n", 1) = 1 unlink("/var/run/sd-agent.pid") = 0 close(3) = 0 munmap(0xb7e0d000, 4096) = 0 rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x589978}, {0xb89a60, [], SA_RESTORER, 0x589978}, 8) = 0 brk(0xa022000) = 0xa022000 exit_group(1) = ? 

作为一般规则(即在香草核心中),由于对于上帝内存 dup_mm 情况dup_mmdup_task_structalloc_pidmpol_dupmm_init等),或者由于security_vm_enough_memory_mm 在执行 过度使用政策时失败了。

首先检查分叉失败的进程的vmsize,然后将其与过度使用策略相关的可用内存量(物理和交换)进行比较(插入数字)。

在您的具体情况下,请注意Virtuozzo在超执行方面有额外的检查 。 而且,我不确定你的容器 ,你真正拥有多less控制权, 交换和过度使用configuration (为了影响执行结果)。

现在,为了实际前进,我会说你剩下两个select

  • 切换到更大的实例,或者
  • 把一些编码工作, 更有效地控制脚本的内存占用

请注意 ,如果事实certificate这不是你,那么编码的努力可能是徒劳的,但是其他一些人在运行amock的同一台服务器上的不同实例中搭配使用。

在内存方面,我们已经知道subprocess.Popen使用了fork / clone ,这意味着每当你调用它时,你就会再次请求更多的内存,就像Python已经吃光了一样 ,也就是在数百个额外的MB中,所有为了然后exec一个可怕的10kB可执行文件,如freeps 。 在不利的过度使用政策的情况下,您很快会看到ENOMEM

没有这个父页表等替代fork副本问题是vforkposix_spawn 。 但是如果你不想用vfork / posix_spawn重写subprocess.Popen suprocess.Popen块,可以考虑在脚本开头(当Python的内存占用最suprocess.Popen使用suprocess.Popen一次,以产生一个shell脚本,然后运行free / ps / sleep和其他与脚本并行的循环 ; 轮询脚本的输出或者同步读取它,如果你有其他的东西需要asynchronous处理的话,可能从一个单独的线程读取它 – 在Python中执行你的数据处理,但把分支留给下级进程。

但是 ,在你的具体情况下,你可以跳过调用psfree 。 无论您是select自己还是通过现有的库和/或包来访问它,您都可以直接从procfs获取这些信息。 如果psfree是你正在运行的唯一实用程序,那么你可以subprocess.Popen完成

最后,无论你做什么subprocess.Popen关心,如果你的脚本泄漏内存,你仍然会打墙。 密切关注,并检查内存泄漏 。

互换可能不是以前build议的红鲱鱼。 在ENOMEM之前有问题的Python进程有多大?

在内核2.6下, /proc/sys/vm/swappiness控制内核转换到多大程度,并且overcommit*文件多less,以及内核如何用一个眨眼和点头来分配内存。 就像你的Facebook关系状态一样, 这很复杂 。

…但交换实际上是根据需要(根据networking主机)…

但不是根据您的free(1)命令的输出,它显示您的服务器实例不识别交换空间。 现在,您的虚拟主机当然可以比我更了解这个主题,但是我使用的虚拟RHEL / CentOS系统已经向客户操作系统报告了可用的交换。

修改Red Hat知识库文章15252 :

只要匿名内存和系统V共享内存的总和小于RAM的大约3/4,红帽企业版Linux 5系统就可以运行得很好,没有交换空间。 …. 4 GB的内存或更less的系统[build议]有至less2GB的交换空间。

将你的/proc/sys/vm设置与一个普通的CentOS 5.3安装进行比较。 添加一个交换文件。 棘轮下来,看看你是否再活着。

看看free -m的输出,在我看来,你实际上没有可用的交换内存。 我不确定在Linux下是否可以自动按需交换,但是我遇到了同样的问题,这里没有任何答案对我有帮助。 然而,添加一些交换内存,在我的情况下解决了这个问题,因为这可能会帮助其他人面临同样的问题,我发表我的答案如何添加1GB的交换(在Ubuntu 12.04,但它应该同样适用于其他发行版)。

您可以先检查是否有任何交换内存启用。

 $sudo swapon -s 

如果它是空的,这意味着你没有任何交换启用。 要添加1GB的交换:

 $sudo dd if=/dev/zero of=/swapfile bs=1024 count=1024k $sudo mkswap /swapfile $sudo swapon /swapfile 

fstab下行添加到fstab以使交换永久。

 $sudo vim /etc/fstab /swapfile none swap sw 0 0 

来源和更多信息可以在这里find。

我继续怀疑你的客户/用户有一些内核模块或驱动程序加载干扰clone()系统调用(也许一些模糊的安全增强,像LIDS但更隐晦?),或者是某种程度上填补了一些内核fork() / clone()操作所需的数据结构(进程表,页表,文件描述符表等)。

以下是fork(2)手册页的相关部分:

错误
        EAGAIN fork()不能分配足够的内存来复制父页表并为其分配任务结构
              儿童。

        EAGAIN由于遇到调用者的RLIMIT_NPROC资源限制,无法创build新进程。 至
              超过此限制,该进程必须具有CAP_SYS_ADMIN或CAP_SYS_RESOURCEfunction。

       由于内存紧张,ENOMEM fork()无法分配必要的内核结构。

我build议让用户在启动到股票,通用内核以及只加载一小部分模块和驱动程序(运行应用程序/脚本所需的最低限度)后尝试。 从那里,假设它在这种configuration下工作,他们可以在二者之间进行二进制search,并在configuration中发现问题。 这是标准的系统pipe理员故障排除101。

strace的相关行是:

 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory) 

…我知道其他人已经谈到了交换和内存的可用性(我会build议你至less设置一个小的交换分区,具有讽刺意味的是,即使它在RAM磁盘上…通过Linux内核的代码path,当它即使可用的交换的一小部分已经比那些可用的交换为零的交换path(exception处理path)得到了更广泛的运用。

不过我怀疑这还是一条红鲱鱼。

free正在报告0(ZERO)内存正在使用的caching和缓冲区是非常令人不安的事实。 我怀疑free输出…和可能你的应用程序问题在这里,是由某些专有的内核模块,以某种方式干扰内存分配引起的。

根据fork()/ clone()的手册页,如果您的调用会导致资源限制违规(RLIMIT_NPROC),则fork()系统调用应该返回EAGAIN …但是,它不会说是否要返回EAGAIN受其他RLIMIT *违规。 无论如何,如果你的目标/主机有一些奇怪的Vormetric或其他安全设置(或者即使你的进程运行在一些奇怪的SELinux策略下),那么它可能会导致这个-ENOMEM失败。

这是一个普通的Linux / UNIX问题。 那里有一些不合标准的东西。

你有没有尝试过使用:

 (status,output) = commands.getstatusoutput("ps aux") 

我认为这已经为我解决了完全相同的问题。 但是后来我的过程最终被杀死了,而不是没有产卵,更糟的是..

经过一些testing,我发现这只发生在旧版本的python上:它发生在2.6.5而不是在2.7.2

我的search导致我在这里python-close_fds问题 ,但不设置closed_fds还没有解决这个问题。 这还是值得一读的。

我发现python泄露文件描述符只是保持眼睛:

 watch "ls /proc/$PYTHONPID/fd | wc -l" 

和你一样,我想捕获命令的输出,而且我想避免OOM错误…但是看起来人们只能使用较less的bug版本的Python。 不理想…

munmap(0xb7d28000,4096)= 0
写(2,“OSError”,7)= 7

我见过这样粗糙的代码:

 serrno = errno; some_Syscall(...) if (serrno != errno) /* sound alarm: CATROSTOPHIC ERROR !!! */ 

你应该检查这是否是在Python代码中发生的事情。 Errno只有在系统调用失败时才有效。

编辑添加:

你没有说这个过程有多久。 可能的内存消费者

  • 分叉的过程
  • 未使用的数据结构
  • 共享库
  • 内存映射文件