【问题标题】:Python subprocess.Popen "OSError: [Errno 12] Cannot allocate memory"Python subprocess.Popen “OSError:[Errno 12] 无法分配内存”
【发布时间】:2010-11-24 22:33:30
【问题描述】:

注意:这个问题最初是在here 提出的,但即使实际上没有找到可接受的答案,悬赏时间也已过期。我正在重新提出这个问题,包括原始问题中提供的所有详细信息。

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

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

脚本使用代码here作为守护进程运行。

作为 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 机器上重现,也无法让任何其他用户报告同样的问题。

按照原始问题中的建议,我已经尝试了很多方法来调试它:

  1. 在 Popen 调用前后记录 free -m 的输出。内存使用没有显着变化,即内存不会随着脚本运行而逐渐用完。

  2. 我在 Popen 调用中添加了 close_fds=True 但这没有任何区别 - 脚本仍然因相同的错误而崩溃。建议herehere

  3. 我按照here 的建议检查了在 RLIMIT_DATA 和 RLIMIT_AS 上都显示 (-1, -1) 的 rlimits。

  4. An article 建议没有交换空间可能是原因,但实际上交换是按需提供的(根据网络主机),这也被认为是虚假原因 here

  5. 进程正在关闭,因为这是使用 Python 源代码和 cmets here 支持的 .communicate() 的行为。

可以在GitHub here 上找到完整的检查,其中 getProcesses 函数从第 442 行定义。从第 520 行开始由 doChecks() 调用。

脚本在崩溃前使用 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)                           = ?

【问题讨论】:

  • 用完“管道”或文件描述符或与这些相关的内核资源?
  • 检查/var/log/messages,或dmesg 命令。
  • 日志中没有与此相关的内容。
  • 你有没有解决这个问题?我有非常相似的症状。我有足够的备用内存,但是在添加交换之后(就像你的一些答案所暗示的那样),问题就消失了。只是想知道从那时到现在的几个月里你是否发现了什么。 -- 谢谢!
  • 我遇到了同样的问题,但没有解决办法——有什么想法吗?

标签: python linux memory


【解决方案1】:

作为一般规则(即在香草内核中),fork/cloneENOMEM occur specifically 失败是因为对上帝诚实的内存不足情况dup_mmdup_task_structalloc_pidmpol_dupmm_init 等等),或者因为 security_vm_enough_memory_mm 失败了你enforcing overcommit policy

首先在尝试分叉时检查未能分叉的进程的 vmsize,然后比较与过度使用策略相关的可用内存量(物理和交换)(将数字插入.)

在您的特定情况下,请注意 Virtuozzo 在overcommit enforcement 中有additional checks。此外,我不确定您在您的容器中对swap and overcommit configuration 真正拥有多少控制权(以影响执行结果。)

现在,为了真正向前迈进,我想说你有两个选择

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

注意,如果发现不是你,而是在你运行 amock 的同一台服务器上的不同实例中配置的其他人,编码工作可能完全是徒劳的。

内存方面,我们已经知道 subprocess.Popen 使用 fork/clone under the hood,这意味着每次您调用它时,您都是在 再次请求Python 已经消耗了很多内存,即额外的数百 MB,所有这些都是为了然后 exec 一个微不足道的 10kB 可执行文件,例如 freeps。在不利的过度使用策略的情况下,您很快就会看到ENOMEM

没有此父页表等复制问题的fork 的替代方案是vforkposix_spawn。但是,如果您不想用 vfork/posix_spawn 重写 subprocess.Popen 的块,请考虑在脚本开头(当 Python 的内存占用最小时)仅使用一次 suprocess.Popen,以 生成一个 shell 脚本,然后运行 ​​free/ps/sleep 以及循环中的任何其他内容 与您的脚本并行;轮询脚本的输出或同步读取它,如果您有其他事情要异步处理,可能从一个单独的线程中读取 - 在 Python 中处理您的数据,但将分叉留给从属进程。

但是,在您的特定情况下,您可以完全跳过调用psfree无论您选择自己访问还是通过 existing libraries and/or packages,您都可以直接从 procfs 在 Python 中轻松获得信息。如果 psfree 是您运行的唯一实用程序,那么您可以完全取消 subprocess.Popen

最后,就subprocess.Popen 而言,无论您做什么,如果您的脚本泄漏内存,您最终还是会碰壁。关注它,check for memory leaks

【讨论】:

  • 我发现在垃圾收集器有一段时间没有运行的情况下,在 subprocess.Popen 之前运行 gc.collect() 会有所帮助。
  • 我写了一个守护进程来处理辅助脚本策略:github.com/SeanHayes/errand-boy 我正在与我的一个客户一起在生产环境中使用它,并且我们的“无法分配内存”问题已经解决了。
  • 我会很感激一个简单的诊断,例如关注/proc/fd/maps 以确定是否过度使用的内存实际上是问题
【解决方案2】:

查看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 以使交换永久化。

$sudo vim /etc/fstab

     /swapfile       none    swap    sw      0       0 

来源和更多信息可以找到here

【讨论】:

  • 这是否解决了同样的问题或其他问题?
  • 这是在 CentOS 6.4 上为我完成的。安装 awstats 时遇到错误,谢谢。
  • 虽然这让我执行了代码,但它并没有真正解决问题,这可能在于我使用的一个库。
  • 你解决了我的问题。谢谢! +1
【解决方案3】:

为了方便解决,您可以

echo 1 > /proc/sys/vm/overcommit_memory

如果您确定您的系统有足够的内存。见Linux over commit heuristic

【讨论】:

  • 非常感谢!如此简单的解决方案,您节省了我的时间)
【解决方案4】:

swap 可能不是之前建议的红鲱鱼。在ENOMEM 之前有问题的python 进程有多大?

在内核 2.6 下,/proc/sys/vm/swappiness 控制内核转向交换的积极程度,overcommit* 文件内核可以通过眨眼和点头来分配内存的数量和精确度。喜欢你的 Facebook 关系状态,it's complicated

...但实际上可以按需提供交换(根据网络主机)...

但不是根据您的free(1) 命令的输出,它显示您的服务器实例无法识别交换空间。现在,您的虚拟主机肯定比我更了解这个主题,但我使用的虚拟 RHEL/CentOS 系统报告说来宾操作系统可用交换。

适应Red Hat KB Article 15252

红帽企业 Linux 5 系统 在没有交换空间的情况下运行得很好 只要匿名的总和 内存和系统 V 共享内存是 不到大约 3/4 的 RAM 量。 .... 内存为 4GB 或更少的系统 [建议]至少 2GB 交换空间。

将您的/proc/sys/vm 设置与普通的 CentOS 5.3 安装进行比较。添加交换文件。拉低swappiness,看看你是否还能活下去。

【讨论】:

  • 检查python进程大小的最佳方法是什么? ps?
  • ps -o user,pid,vsz="Mem(Kb)" -o cmd $PYTHON_PID 或 top(1) 之类的东西应该这样做。
【解决方案5】:

我仍然怀疑您的客户/用户加载了一些内核模块或驱动程序 正在干扰clone() 系统调用(可能是一些模糊的安全增强, 像 LIDS 但更晦涩的东西?)或者以某种方式填充了一些内核数据 fork()/clone() 运行所必需的结构(进程表,页面 表、文件描述符表等)。

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

错误 EAGAIN fork() 无法分配足够的内存来复制父页表并为 孩子。 EAGAIN 无法创建新进程,因为遇到调用者的 RLIMIT_NPROC 资源限制。到 超过此限制,进程必须具有 CAP_SYS_ADMIN 或 CAP_SYS_RESOURCE 功能。 由于内存紧张,ENOMEM fork() 未能分配必要的内核结构。

我建议让用户在启动到一个普通的通用内核并且只加载最少的模块和驱动程序集(运行应用程序/脚本所需的最低限度)之后尝试这个。从那里开始,假设它在该配置中工作,他们可以在该配置和出现问题的配置之间执行二进制搜索。这是标准的系统管理员故障排除 101。

strace 中的相关行是:

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

...我知道其他人已经讨论过交换和内存可用性(我建议您至少设置一个小的交换分区,具有讽刺意味的是,即使它在 RAM 磁盘上...通过 Linux 内核的代码路径当它有一点点可用的交换时,它比那些(异常处理路径)可用的交换量为零的那些(异常处理路径)广泛得多。

但是我怀疑这仍然是一个红鲱鱼。

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

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

这不太可能是普通的 Linux/UNIX 问题。那里发生了一些非标准的事情。

【讨论】:

  • 服务器在使用 Virtuozzo 进行虚拟化的媒体模板 (dv) 基础上运行。
  • 尝试搜索 Virtuozzo 留言板和错误跟踪系统,或许还可以寻找 Virtuozzo 子系统本身的升级。
【解决方案6】:

您是否尝试过使用:

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

我认为这为我解决了完全相同的问题。 但是后来我的进程最终被杀死而不是未能生成,这更糟糕..

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

我的搜索将我带到了这里 python-close_fds-issue,但取消设置 closed_fds 并没有解决问题。还是值得一读的。

我发现 python 正在泄漏文件描述符,只需要留意它:

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

和你一样,我确实想捕获命令的输出,并且确实想避免 OOM 错误……但看起来唯一的方法是人们使用错误较少的 Python 版本。不理想...

【讨论】:

    【解决方案7】:

    也许你可以简单地

    $ sudo bash -c "echo vm.overcommit_memory=1 >> /etc/sysctl.conf"
    $ sudo sysctl -p
    

    它适用于我的情况。

    参考:https://github.com/openai/gym/issues/110#issuecomment-220672405

    【讨论】:

      【解决方案8】:

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

      我见过像这样的草率代码:

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

      您应该检查一下这是否是 蟒蛇代码。 Errno 仅在进行系统调用时有效 失败了。

      编辑添加:

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

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

      【讨论】:

      • 是的,但是我们从 OP 的 strace 中看到,第一个系统调用失败——来自 clone()——是 ENOMEM,正如所报告的那样。这个错误在 python 的低内存故障中通过回溯构造保留下来,即使 C 库 errno 在此过程中被重置了很多次。
      猜你喜欢
      • 2013-08-01
      • 2015-05-04
      • 1970-01-01
      • 2010-11-16
      • 2017-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-23
      相关资源
      最近更新 更多