Python subprocess.Popen“OSError:[Errno 12] 无法分配内存”

2024-10-12 09:08:00
admin
原创
96
摘要:问题描述:注意:这个问题最初是在这里提出的,但悬赏时间已过,尽管实际上没有找到可接受的答案。我重新提出这个问题,包括原始问题中提供的所有详细信息。一个 Python 脚本使用sched模块每 60 秒运行一组类函数:# sc is a sched.scheduler instance sc.enter(60,...

问题描述:

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

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

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

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

作为 doChecks 的一部分调用的许多类方法使用子进程模块调用系统函数以获取系统统计信息:

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,但这并没有什么不同 - 脚本仍然因相同的错误而崩溃。建议在这里和这里。

  3. 我检查了 rlimits,结果显示 RLIMIT_DATA 和 RLIMIT_AS 上都显示 (-1, -1),正如这里所建议的。

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

  5. 这些进程被关闭是因为这是使用 .communicate() 的行为,正如 Python 源代码和此处的注释所支持的那样。

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

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

recv(4, "Total Accesses: 516662
Total 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)
", 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:
        print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0
try"..., 4096) = 4096
read(8, " p2cread
        # c2pread    <-"..., 4096) = 4096
write(2, "    ", 4)                     = 4
write(2, "errread, errwrite)
", 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:
        print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0
try"..., 4096) = 4096
read(8, " p2cread
        # c2pread    <-"..., 4096) = 4096
read(8, "table(self, handle):
           "..., 4096) = 4096
read(8, "rrno using _sys_errlist (or siml"..., 4096) = 4096
read(8, " p2cwrite = None, None
         "..., 4096) = 4096
write(2, "    ", 4)                     = 4
write(2, "self.pid = os.fork()
", 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, "
", 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)                           = ?

解决方案 1:

作为一般规则(即在 vanilla 内核中),/fork失败cloneENOMEM 发生通常是因为真的内存不足dup_mm、、、等等) 或者是因为强制执行过度提交策略失败dup_task_structalloc_pid`mpol_dupmm_initsecurity_vm_enough_memory_mm`

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

在您的特定情况下,请注意 Virtuozzo在过度使用执行方面有额外的检查。此外,我不确定您在容器内部对交换和过度使用配置(以影响执行结果)有多少控制权。

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

  • 切换到更大的实例,或者

  • 投入一些编码精力来更有效地控制脚本的内存占用

请注意,如果结果发现不是你,而是其他人在同一台服务器上的不同实例中运行失控,那么编码工作可能就毫无意义了。

内存方面,我们已经知道在底层subprocess.Popen使用了fork/clone ,这意味着每次调用它时,你都会再次请求与 Python 已经占用的内存一样多的内存,即数百 MB 的额外内存,所有这些都是为了运行一个微不足道的 10kB 可执行文件,例如或。在不利的过度使用策略的情况下,你很快就会看到。exec`freepsENOMEM`

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

但是,在您的特定情况下,您可以完全跳过调用psfree您可以直接从 Python 中获取该信息procfs,无论您选择自己访问还是通过现有库和/或包访问。如果psfree是您运行的唯一实用程序,那么您可以完全取消subprocess.Popen

最后,无论你做什么subprocess.Popen,如果你的脚本泄漏内存,你最终还是会遇到困难。密切关注,检查是否有内存泄漏。

解决方案 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 

您可以在这里找到来源和更多信息。

解决方案 3:

一个简单的解决方法是,你可以

echo 1 > /proc/sys/vm/overcommit_memory

如果你确定你的系统有足够的内存。请参阅Linux 过度提交启发式方法。

解决方案 4:

swap 可能不是之前提到的转移注意力的话题。在之前,有问题的 Python 进程有多大ENOMEM

在内核 2.6 中,/proc/sys/vm/swappiness控制内核转向交换的积极程度,并overcommit*记录内核分配内存的量和精确程度。就像您的 Facebook 关系状态一样,它很复杂。

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

但根据free(1)命令的输出,结果并非如此,它显示服务器实例未识别任何交换空间。现在,您的网络主机肯定比我更了解这个主题,但我使用过的虚拟 RHEL/CentOS 系统报告了客户操作系统可用的交换空间。

调整Red Hat 知识库文章 15252:

只要匿名内存和系统 V 共享内存的总和小于 RAM 数量的 3/4,Red Hat Enterprise Linux 5 系统就可以在没有交换空间的情况下正常运行。......具有 4GB 或更少 RAM 的系统
[建议]至少有 2GB 的交换空间。

将您的/proc/sys/vm设置与普通的 CentOS 5.3 安装进行比较。添加交换文件。降低配置swappiness,看看您是否可以存活更久。

解决方案 5:

我继续怀疑您的客户/用户加载了一些内核模块或驱动程序,这些模块或驱动程序正在干扰系统调用(可能是一些模糊的安全增强功能,像 LIDS 这样更模糊的东西?)或者以某种方式填充了一些运行所必需的内核clone()数据结构(进程表、页表、文件描述符表等)。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 ... 但是,它没有说明其他 RLIMIT* 违规是否应返回 EAGAIN。无论如何,如果您的目标/主机具有某种奇怪的 Vormetric 或其他安全设置(或者即使您的进程在某些奇怪的 SELinux 策略下运行),那么它可能会导致此 -ENOMEM 故障。

这不太可能是普通的 Linux/UNIX 问题。您遇到了一些非标准问题。

解决方案 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 !!! */

您应该检查 Python 代码中是否发生了这种情况。仅当正在进行的系统调用失败时,Errno 才有效。

编辑后添加:

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

  • 分叉进程

  • 未使用的数据结构

  • 共享库

  • 内存映射文件

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用